LoginSignup
0
0

オシロスコープで静止画を描画する

Last updated at Posted at 2024-03-16

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を実行し波形を観察する。

最初に円が表れ、次に自分の変換した画像が表示されれば成功。
色が薄く見えてしまう場合、輝度を上げると改善される可能性あり。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0