1.はじめに
使用したもの
・オシロスコープ
・ノートパソコン(Pythonの開発環境)
・NIダック(型番:NI USB-6212)
開発環境
・Python 3.8.6
・ライブラリ
nidaqmx(0.8.0)
nidaqmxのダウンロード
matplotlib(3.7.3)
numpy(1.24.4)
cv2
time
使用したソフト
・VScode editor
・NI MAX(2023Q4)
NI MAXのダウンロード
2.仕組み
オシロスコープで静止画を表示するために、今回はラスター方式というものを用いた。
これはテレビにも用いられる方式で、水平方向をX座標、垂直方向をY座標とし、ある画素の位置を (x, y) のように表現するものである。
今回は座標(x, y)に対応する電圧をNIダックを用いて出力し、それをオシロスコープに接続することで静止画を描画している。NIダックはPythonによって動作させるため、専用のライブラリが必要となる。
3.画像変換プログラム
まず、カラー画像を白黒に変換するプログラムを作成した。
img = cv2.imread('example.png', 0)
ret, bi_img = cv2.threshold(img, 180, 255, cv2.THRESH_BINARY)
cv2.imshow("Binary", bi_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
b_img = np.array(bi_img)
x0 = []
y0 = []
for i in range (b_img.shape[0]):
for j in range (b_img.shape[1]):
if b_img[i, j] == 0:
x0.append(j/100)
y0.append(-i/100)
このコードの example.png に変換したいファイル名を入れる。
2行目の180という数字は閾値を表しており、これを大きくすることでより多くの領域が白色(255)に変換される。
最後にx0, y0というリストを作成し、ここに変換した画像のうち黒色(0)の部分の座標を格納していく。この時、描画する画像が上下反転せず正常に表示されるようにするため、y座標の符号を逆転させている。また座標を1/100倍することで大きさを調整している。(任意で変更可)
実行したときに変換画像が表示され、閾値の調整がうまくいっているか確認できるようにした。
参考にしたサイト
4.画像描画プログラム
画像描画プログラム全体は以下のようになった。
import cv2
import time
from nidaqmx.constants import AcquisitionType
import numpy as np
import nidaqmx
'''画像変換プログラム'''
img = cv2.imread('example.png', 0)
ret, bi_img = cv2.threshold(img, 180, 255, cv2.THRESH_BINARY)
cv2.imshow("Binary", bi_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
b_img = np.array(bi_img)
x0 = []
y0 = []
for i in range (b_img.shape[0]):
for j in range (b_img.shape[1]):
if b_img[i, j] == 0:
x0.append(j/100)
y0.append(-i/100)
'''initiation(初期設定)'''
global tTask1
tTask1=nidaqmx.Task() #アナログ出力用のタスク
tTask1.__init__("main_VC_controller")#タスク名の割り当て
'''サンプル波形のパラメータ'''''''''''''''''''''''''''''''''''''''''''''
samps_rate = 100000 #サンプリングレート
N = 1000 #バッファサイズ
n = 1 #三角関数の周期を変えるための整数
data_size = 1000 #1サンプルの配列データ要素数(データサイズ)
required_value = 100 #コールバック関数をトリガするための端末読み取りデータ数
#初期波形の定義
t= np.linspace( -2*np.pi, 2*np.pi, num=data_size, endpoint=True )
Vx = 2*np.cos(t)
Vy = 2*np.sin(t)
'''出力するサンプル波形*(numpyの1次元ndarray配列によって指定)'''
sample1 = np.append( Vx, np.zeros(1))
sample2 = np.append( Vy, np.zeros(1))
sample_2D = np.array([sample1, sample2])
sample_2D__ = np.array([ Vx, Vy ])
on_off=True
global samp_Writer
k = 0
j = 0
'''コールバック関数の定義'''''''''''''''''''''''''''''''''''''''''''''
def askUser():
global on_off
input("Press return to stop")
on_off = False
def callback1(task_handle, status, callback_data=1):
print('コールバックしました')
return 0
def callback2(task_idx, event_type, num_samples, callback_data):
global Vx
global Vy
global sample_2D__
global k
global j
#波形の出力
while True:
print('コールバック関数に移動しました。')
Vx = x0
Vy = y0
sample_2D__ = np.array([Vx, Vy], dtype=float)
samp_Writer.write_many_sample(sample_2D__)
#print("kの値:",k)
j = j + 1
'''アナログ出力信号のタスクを構成(DAQ内の構成)'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
tTask1.ao_channels.add_ao_voltage_chan( physical_channel='Dev1/ao0:1',
name_to_assign_to_channel="AO-code",
min_val=-10, max_val=10 )#タスク1
tTask1._out_stream.output_onbrd_buf_size = 4095
tTask1.output_buf_size = N #バッファサイズ
tTask1.timing.cfg_samp_clk_timing(rate=samps_rate,
sample_mode=AcquisitionType.CONTINUOUS,
samps_per_chan=N)#有限サンプルの出力タイミング
'''波形の生成(PC→DAQの構成)'''''''''''''''''''''
#波形出力の自動スタート、つまり、垂れ流し状態。生成されたそばから出力している
samp_Writer = nidaqmx.stream_writers.AnalogMultiChannelWriter(tTask1.out_stream, auto_start=False)
#第一引数に指定したサンプル数がPCバッファ→DAQデバイスに書き込まれたときに受信するコールバック関数を登録する
tTask1.register_every_n_samples_transferred_from_buffer_event(required_value, callback2)
#tTask1.register_every_n_samples_transferred_from_buffer_event(1000, callback2)
#これを行うとき、明示的なタスクの開始でなくてはならず、auto_startは使用不可である
samp_Writer.write_many_sample(sample_2D__)
'''実行フェーズ'''
print('開始準備中...')
time.sleep(3)
print('タスクを開始')
tTask1.start()#タスク1の開始
while k < 10:
Vx = 0.5*k*np.cos(t)
Vy = 0.5*k*np.sin(t)
sample_2D__ = np.array([Vx, Vy])
samp_Writer.write_many_sample(sample_2D__)
print('連続出力')
'''停止フェーズ'''
#print(tTask1.is_task_done())#タスクが終わっているかどうかをTure or Falseで値を返す
print('タスクの停止まで待機しています...')
tTask1.wait_until_done(timeout=5)#タスクの停止までの待機時間(有限サンプルモードのときにして指定する)
#print(tTask1.is_task_done())
print('タスクの停止')
tTask1.stop()#タスクを停止
print('タスク1をクリアしました')
tTask1.close()#生成したタスクをクリア
重要なのは「コールバック関数の定義」の部分だ。ここで
Vx = x0
Vy = y0
sample_2D__ = np.array([Vx, Vy], dtype=float)
samp_Writer.write_many_sample(sample_2D__)
#print("kの値:",k)
このように指定することで、(x, y)座標を電圧として出力できるようになっている。
また、このコードでは最初に初期波形(今回は円)が表示されるようになっているが、この部分を消去しようとしてもうまくいかなかったため、残していただきたい。
5.実際に動作させる
・PCとNIダックをUSB接続し、NI MAXに認識されていることを確認する。
・NIダックとオシロスコープを接続する。
※この時、今回のコードではAO0、AO1が(x, y)出力に対応していることに注意。
・オシロスコープのスケールを適切な値に調整する。例えば画像サイズ256×256の場合、±5V程度がちょうどよい。
・Pythonを実行し波形を観察する。
最初に円が表れ、次に自分の変換した画像が表示されれば成功。
色が薄く見えてしまう場合、輝度を上げると改善される可能性あり。