
26日の月(Super moon)

AMG8833を利用してサーモイメージャーを作ってみる。
左からDiymore MAX30102 心拍数センサーモジュール、8x8サーモイメージャー(AMG8833)、VL53L0X (ToF) レーザー測距センサ
センサーサイズ8×8の情報をbicubic補間でなめらかに表示。
前回は露光時間1秒で撮影し、画像が長く流れたので今回は20mSで撮影してみたが、まだ画像が流れていて露光時間の長すぎるようだ。次回は1mSを試してみよう。
木星と土星が見えていたので、撮影してみた。約1000枚を撮影した後に、Regstaxというフリーソフトを利用して位置合わせをしながらスタックし、特徴抽出のためにWavelet変換処理を実行します。よくみると土星の輪の中に、カッシーニの間隙と呼ばれる隙間も写っている。惑星の撮影には、もう少し高倍率が必要かも;
ToF(Time of Flight)デバイスVL530Xを利用して、超近接レーダもどきを作ってみた。
主要なパーツ
VL53L0X Time-of-Flight 距離センサモジュールの概要は、例えばこちらのページを参照。
arduino nanoでステッピングモータを回転させながら、ToFデバイスで光を反射する物体までの距離を測定します。モータの回転角度と距離をUSB I/FでPCへ転送し、PC側でPython(Jupyter notebook)で可視化しています。
Jupyter notebookで可視化した様子。
Jupyter notebookで可視化
# -*- coding: utf-8 -*-
import sys
import glob
import serial
import asyncio, sys
import cv2
import numpy as np
import pygame
import math
from pygame.locals import *
def main():
hist=350
buf={}
(x,y) = (1024,1024) # 画面サイズ
pygame.init() # pygame初期化
pygame.display.set_mode((x, y), 0, 32) # 画面設定
pygame.display.set_caption('ToF VL53L0X ranging...')
screen = pygame.display.get_surface()
fontFace =cv2.FONT_HERSHEY_SIMPLEX
dev=glob.glob("/dev/ttyUSB*")[0]
dev=dev.replace('[','')
dev=dev.replace(']','')
ser =serial.Serial(dev, 115200)
for i in range(5):
line=ser.readline()
font = pygame.font.Font(None, 55)
#print(buf)
while (1):
for i in range(1,3):
try:
deg,distance=ser.readline().decode().split(',')
deg=int(deg)
distance=int(distance)
buf[deg]=distance
# ゴミデータ対策
for k in range(4):
tmp=deg-k
if tmp<0:
tmp=tmp+360
buf[deg-k]=distance
except:
continue
# レーダー画面の背景描画
pygame.draw.circle(screen, (0, 200, 0), (int(x/2), int(y/2)), int(x/2), 1)
pygame.draw.circle(screen, (0, 200, 0), (int(x/2), int(y/2)), int(x/4), 1)
pygame.draw.line(screen, (0, 200, 0), (0, int(y/2)), (x, int(y/2)))
pygame.draw.line(screen, (0, 200, 0), (int(x/2), 0), (int(x/2), y))
# レーダービームの軌跡描画
dx = x/2 + x/2 * math.cos(math.radians(deg))
dy = y/2 + x/2 * math.sin(math.radians(deg))
pygame.draw.line(screen, (0, 255, 0), (int(x/2), int(y/2)), (int(dx), int(dy)))
for k in range(hist):
tmp = deg-k
if tmp<0:
tmp=tmp+360
if tmp in buf:
x0 = x/2 + buf[tmp]*0.8*math.cos(math.radians(tmp))
y0= y/2 + buf[tmp]*0.8*math.sin(math.radians(tmp))
blip=int(255*(hist-k)/hist)
pygame.draw.line(screen, (blip, 0, 0), (int(x/2), int(y/2)), (int(x0), int(y0)))
pygame.draw.circle(screen, (blip, 0, 0), (int(x0), int(y0)), 4, 2)
pygame.display.update() # 画面更新
pygame.time.wait(10) # 更新時間間隔
screen.fill((0, 20, 0, 0)) # 画面の背景色
# イベント
for event in pygame.event.get():
if event.type == QUIT: # 閉じるボタンが押されたら終了
pygame.quit() # Pygameの終了(画面閉じられる)
ser.close()
sys.exit()
if __name__ == "__main__":
main()
Arduinoで(ステッピングモータと距離センサーの制御)
#include <vl53l0xTOFA.h>
#include <Wire.h>
#include <Stepper.h>
//#define PIN_xDIR D3 //direction
//#define PIN_xSTEP D4 //step
#define PIN_xDIR PD3 //direction
#define PIN_xSTEP PD2 //step
#define PIN_MODE A1 // MODE
//#define LED A2
#define ST 4
VL53L0xTOFA sensor;
// for your motor
int val = 0;
// initialize the stepper library on pins 8 through 11:
//Stepper myStepper(stepsPerRevolution, D4, D3);
float dist_TOF(int pos, int n) {
float sum = 0;
for ( int j = 0; j < n; j++) {
sensor.readTOFA();
sum += sensor.tofa.distancemm;
}
int distance = int(sum / n);
int deg = int(pos * 36.0 / 40.0);
// Serial.print(pos);
// Serial.print(',');
Serial.print(deg);
Serial.print(',');
Serial.println(distance);
}
void setup() {
pinMode(PIN_xDIR, OUTPUT);
pinMode(PIN_xSTEP, OUTPUT);
// pinMode(LED, OUTPUT);
pinMode(PIN_MODE, INPUT_PULLUP);
// set the speed at 60 rpm:
// myStepper.setSpeed(60);
// initialize the serial port:
Serial.begin(115200);
delay(1000);
Wire.begin();
delay(1000);
sensor.setTimeout(500);
if (!sensor.init())
{
Serial.println("Failed to detect and initialize sensor!");
while (1) {}
}
sensor.startContinuous(5);
Serial.println("Radar Start");
}
void rot(int dir, int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < 2 * ST; j++) {
digitalWrite(PIN_xSTEP, !(digitalRead(PIN_xSTEP)));
delayMicroseconds(800);
}
dist_TOF(i, 1);
delayMicroseconds(800);
}
}
void loop() {
if (digitalRead(PIN_MODE) == LOW) {
digitalWrite(PIN_xDIR, LOW);
rot(1, 400);
} else {
digitalWrite(PIN_xDIR, HIGH);
rot(0, 400);
}
}
国際宇宙ステーション(ISS)は、日本、米国、ロシア、カナダ、欧州の15カ国が協力して建設した、地上約400km上空にある人類史上最大の有人実験施設。その大きさは約108.5m×72.8mとほぼサッカー場ほどの大きさとなり、質量は約420トンにもなる。
F2,F15,F4EJ,T4,E2Cの写真を深層学習したデータで、動画から機種を認識させる実験。写真の総数は約2100枚。写真は、ネット上で公開されている動画から静止画に変換したものです。ある記事によると、1種類につき1000枚程度が基準(?)で、10000以上が望ましいらしい。70%以上の確率で機種を判定できたときに、機種に対応した色付きの四角で囲み、判定した機種名と確率を表示しています。写真の枚数が少ないためか(?)、誤認識も多い。
https://pjreddie.com/darknet/yolo/
You only look once (YOLO) is a state-of-the-art, real-time object detection system. On a Pascal Titan X it processes images at 30 FPS and has a mAP of 57.9% on COCO test-dev.
注文していたマクロアダプターが追跡情報の更新がないまま届きました。13/21/31mmの3個がセットとなっています。左側がアダプター、右側がZWOのCCDカメラで、色が良く似ています。
本来、レンズとカメラボディの間に取り付けるアダプターなので、CCDカメラと接続するには工夫が必要です。13mmと21mmのアダプターを使って、CCDカメラと接続してみたところ、望遠端付近ではピントが出ました。広角側ではピントが出ませんでした。
強度などの問題があるかもしれませんが、3DプリンターでマクロアダプターとCCDカメラを接続するアダプターを作ってみました。2つのパーツで構成し、M3ネジで接続しています。CCDカメラとは3Dプリンターで作成したM42,0.75Pのネジで接続しています。CADソフトはホビー用途であれば無料で利用できるFusion360を使っています。
EFレンズに接続した様子と、フォーカスのハンド・コントローラ。
ソースコードの最新版は、こちら
組み立ててはみたものの、残念ながら動作が不安定です。今のところ、EF-S 18-55mmと同ISは、それなりに制御できますが、EF 35-80mmとEF70-200 1:28Lはまったく動きません。ロジックアナライザーでデコードしたSPIのデータ観測すると、どのレンズでも同じように見えるのですが、、、
本来、SPIの信号はボード内の短い距離の伝送用なので、コントローラの線が長すぎるのかもしれません。今後、配線の短縮またはバッファーやレベル変換を追加して、改善するかどうか確認したいと思っています。
(追記)ケーブルを約70cmから約25cmに変更したら、手持ちのレンズ全てが一応動くようになりました。ただし、まだ不安定な面もあるので、さらに短くする必要がありそうです。SPIの信号を生成するマイクロプロセッサー(Arduino nano)を、レンズに近接して配置できるよう取り付け方法の変更が必要かもしれません。この場合、ピントの調整を操作するSW付きのロータリーエンコーダの部分だけ、ケーブルで接続した別の箱に格納した方が良さそうです。
ArduinoでEFレンズを制御する実験のメモです。カメラレンズで星雲などを撮影しようとすると、手動でピントを合わせる必要がありますが、微妙な操作が必要でなかなかピントを合わせるのに苦労します。そこで、ステッピングモータでピントを調整しようと、3Dプリンターでこんなものを作ってみました。そうこうしているうちに、CANONのEFレンズをArduinoで制御できるらしいとの情報を得て、自分でも実験してみることに。
レンズの制御信号を取り出せるマウントアダプターを注文したのですが、到着にはしばらく時間がかかりそうなので、手持ちのカメラレンズ(EF-S 18-55mm USM)から信号を取り出すために、7本のリード線を半田づけします。(あくまでも自己責任で、、)
この作業には、こちらのリンクが非常に参考になりました。大型のレンズでは、レンズを駆動するモータ用として6Vの別電源が必要のようですが、実験に使ったレンズではロジック回路と同じ5Vで問題なく駆動できています。
主な材料
秋月の2色LED付きロータリーエンコーダ
OLED 128×64 I2C ディスプレイ
Arduino NANO
回路図
実験に使ったArduno NANOのスケッチ
不用な変数などが残っていますが、とりあえず公開します。(実用化には、まだまだ課題が残っています。)制御の対象はフォーカスと絞りの2種類です。ロータリーエンコーダのSWの長押しで、赤と緑のLEDがトグルで点灯します。赤の点灯中にSWを押してフォーカスを変更し、緑のLED点灯中にSWを押すと絞り値を変更できます。
/*
ロータリーエンコーダの参照コード
https://jumbleat.com/2016/12/17/encoder_1/
*/
/*
EFレンズの制御関連参考リンク
ASCOM EF Lens Controller
http://www.indilib.org/media/kunena/attachments/3728/ascom_efEN.pdf
EFレンズから信号線の引き出の参照資料
How to Move Canon EF Lenses Yosuke Bando
http://web.media.mit.edu/~bandy/invariant/move_lens.pdf
Canon EFレンズのArduino制御
http://otobs.org/hiki/?EOS_model
Technical aspects of the Canon EF lens mount
http://www.eflens.com/lens_articles/ef_lens_mount.html
*/
#include <SPI.h>
#include <EEPROM.h>
#include <math.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
//Adafruit_SSD1306 display(1);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define RUN
#define LED_SW 7
#define ENC_A 2
#define ENC_B 3
#define LED_Red 14
#define LED_Green 15
volatile byte pos;
volatile int enc_count;
boolean sw = false;
int mode = 0;
int mode_counter[2];
int focuserPosition, targetPos, apValue, offset, apAddr, x, y;
String targetStr, apStr, gStr;
boolean IsMoving, IsFirstConnect;
char inStr[6];
void InitLens()
{
SPI.transfer(0x0A);
delay(30);
SPI.transfer(0x00);
delay(30);
SPI.transfer(0x0A);
delay(30);
SPI.transfer(0x00);
delay(30);
}
int ENC_COUNT(int incoming) {
static int enc_old = enc_count;
int val_change = enc_count - enc_old;
if (val_change != 0)
{
incoming += val_change;
enc_old = enc_count;
}
return incoming;
}
void ENC_READ() {
byte cur = (!digitalRead(ENC_B) << 1) + !digitalRead(ENC_A);
byte old = pos & B00000011;
byte dir = (pos & B00110000) >> 4;
if (cur == 3) cur = 2;
else if (cur == 2) cur = 3;
if (cur != old)
{
if (dir == 0)
{
if (cur == 1 || cur == 3) dir = cur;
} else {
if (cur == 0)
{
if (dir == 1 && old == 3) enc_count++;
else if (dir == 3 && old == 1) enc_count--;
dir = 0;
}
}
pos = (dir << 4) + (old << 2) + cur;
}
}
void setup() {
digitalWrite(13, LOW); // SPI Clock PIN
pinMode(ENC_A, INPUT_PULLUP);
pinMode(ENC_B, INPUT_PULLUP);
pinMode(LED_Red, OUTPUT);
pinMode(LED_Green, OUTPUT);
pinMode(LED_SW, INPUT_PULLUP);
// pinMode(MIN, INPUT_PULLUP);
// pinMode(MAX, INPUT_PULLUP);
attachInterrupt(0, ENC_READ, CHANGE);
attachInterrupt(1, ENC_READ, CHANGE);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
// テキストサイズを設定
display.setTextSize(3);
// テキスト色を設定
display.setTextColor(WHITE);
display.setCursor(0, 10);
display.println("EF-LensFocuser");
display.display();
delay(1000);
mode = 0;
apAddr = 0;
focuserPosition = 5000;
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV128);
SPI.setDataMode(SPI_MODE3);
digitalWrite(12, HIGH);
InitLens();
digitalWrite(LED_Red, HIGH);
digitalWrite(LED_Green, LOW);
apValue=EEPROM.read(apAddr);
Serial.begin(9600);
Serial.println(apValue);
// nothing to do inside the setup
}
void loop() {
int sw_count;
short counter_now;
sw_count = 0;
while (digitalRead(LED_SW) == LOW) {
sw_count++;
if (sw_count > 50) {
if (mode == 1) { // 新モードはフォーカス制御
digitalWrite(LED_Red, HIGH);
digitalWrite(LED_Green, LOW);
} else { // 新モードは絞り制御
digitalWrite(LED_Green, HIGH);
digitalWrite(LED_Red, LOW);
}
}
delay(10);
}
delay(100);
if (sw_count > 50) {
if (mode == 0) {
mode = 1; // 絞りモード
digitalWrite(LED_Green, HIGH);
digitalWrite(LED_Red, LOW);
} else {
mode = 0; // フォーカスモード
digitalWrite(LED_Red, HIGH);
digitalWrite(LED_Green, LOW);
}
}
if (sw_count != 0 && (sw_count < 50) ) {
if (mode == 0 ) { // Send command to LENS フォーカス
targetPos = mode_counter[mode] ;
offset = mode_counter[mode] ;
x = highByte(offset);
y = lowByte(offset);
InitLens();
IsMoving = true;
Serial.print(offset); Serial.print(",");
Serial.print(x); Serial.print(",");
Serial.println(y);
SPI.transfer(68); delay(30);
SPI.transfer(x); delay(30);
SPI.transfer(y); delay(30);
SPI.transfer(0); delay(100);
IsMoving = false;
focuserPosition = targetPos;
} else { // 絞り
apValue = mode_counter[mode] % 20;
if (apValue != EEPROM.read(apAddr))
{
InitLens();
Serial.println("AP");
SPI.transfer(0x07); delay(10);
SPI.transfer(0x13); delay(10);
SPI.transfer((apValue - EEPROM.read(apAddr)) * 3);
delay(100);
SPI.transfer(0x08); delay(10);
SPI.transfer(0x00); delay(10);
EEPROM.write(apAddr, apValue);
}
}
}
counter_now = ENC_COUNT(mode_counter[mode]);
if (mode_counter[mode] != counter_now)
{
mode_counter[mode] = counter_now;
}
disp_update();
}
void disp_update() {
display.clearDisplay();
display.setCursor(0, 10);
display.print(" F:");
display.println( mode_counter[0] );
display.print(" A:");
display.println(mode_counter[1]);
display.display();
}
osmo-fl2kで生成したGPS信号の周波数補正
osmo-fl2kには、fl2k_testコマンドが用意されていて、実行すると10秒間隔でPPMエラーを表示します。
$ fl2k_test
Kernel mass storage driver is attached, detaching driver. This may take more than 10 seconds!
Reporting PPM error measurement every 10 seconds...
Press ^C after a few minutes.
real sample rate: 99647567 current PPM: -3524 cumulative PPM: -3524
real sample rate: 99909730 current PPM: -903 cumulative PPM: -2187
real sample rate: 99976679 current PPM: -233 cumulative PPM: -1527
real sample rate: 99994151 current PPM: -58 cumulative PPM: -1156
real sample rate: 100001137 current PPM: 11 cumulative PPM: -921
real sample rate: 100003392 current PPM: 34 cumulative PPM: -761
real sample rate: 100007238 current PPM: 72 cumulative PPM: -641
これは、USB-VGAデバイスを接続した直後の実行例ですが、かなり変動しています。しばらくして試すと、次のように安定してきます。(ただし、CPUの負荷による影響もあるようなので、テスト中は、負荷の重いプロセスの起動は避けたほうがよさそう、、、)
$ fl2k_test
Reporting PPM error measurement every 10 seconds...
Press ^C after a few minutes.
real sample rate: 100006806 current PPM: 68 cumulative PPM: 68
real sample rate: 100006831 current PPM: 68 cumulative PPM: 68
real sample rate: 100007186 current PPM: 72 cumulative PPM: 69
real sample rate: 100005995 current PPM: 60 cumulative PPM: 67
この値を(平均値など)、フローグラフの変数 ID:ppmへセットし、ID:out_rateに表示される(この例では138.01e6)を fl2k_fileで送信する際のサンプルング周波数にセットしたところ、GSP受信機でfixできました。
$fl2k_file -s 138.01e6 ファイル名
安定度などの問題があるかもしれませんが、GPS-SDR-SIMとUSB-VGAアダプタの組み合わせで、超安価なGPS信号シミュレータが実現できそうです。
実行中の動画