赤外線アレイセンサ(AMG8833)を使って、非接触体温計に挑戦 ~完結編~

赤外線アレイセンサを使って、非接触体温計として体温測定を行ったので紹介します。具体的には、センサ温度と測定距離の補正値を算出して反映しました。(精度を上げるには、センサ温度の間隔を0.5℃くらいで細かく区切って、近似式を算出した方が良いと思います。)

  • センサ温度(センサ内サーミスタ温度)を変化させて補正値を算出

センサ温度が上がると、出力温度が下がる傾向にあるので、測定環境での誤差を最小限にするため補正値を算出します。(数時間使っているとセンサ内のサーミスタ温度が数℃変化するのでこれを考慮しないと時間とともに測定結果がずれてきます。)

センサ温度と出力温度(補正値)のグラフ
  • 測定対象物までの距離を変化させて補正値を算出
    測定対象とセンサまでの距離は、出力温度に対して、負の特性を持つことが分かりました。実測してみると、測定範囲(60cm~20cm)の間でも2.4℃くらい変化量があります。測距センサで対象物の距離を測定して、補正値に反映しました。
出力温度と距離の関係グラフ

何だか、いろいろ試していると、実機確認もこんな感じになってしまいました・・・

ラズパイとの配線接続図は以下です。

ラズパイとの配線接続図

参考までに測定で使用した簡単なプログラム(python)です。あまり特殊なことはやっていないので、皆さんの参考になれば幸いです。補正値などは個体差があるので、ご自身の環境で測定して算出する必要があります。もっと良いやり方や間違いの指摘などは大歓迎です。皆で良いものを作ってコロナ禍を乗り越えましょう!

# -*- coding: utf-8 -*-
import cv2
import numpy as np
import time
from signal import signal, SIGPIPE, SIG_DFL
import RPi.GPIO as GPIO

import busio
import board

from PIL import ImageFont, ImageDraw, Image

import matplotlib.pyplot as plt

import smbus
import adafruit_amg88xx

import datetime
#import threading

import csv

# I2Cバスの初期化
i2c_bus = busio.I2C(board.SCL, board.SDA)
i2c = smbus.SMBus(1)
i2c_adr = 0x69

## センサーの初期化
sensor2 = adafruit_amg88xx.AMG88XX(i2c_bus, addr=i2c_adr)
## センサーの初期化待ち
time.sleep(.1)

# AMG8833追加設定
# フレームレート(0x00:10fps, 0x01:1fps)
i2c.write_byte_data(i2c_adr, 0x02, 0x00)
# INT出力無効
i2c.write_byte_data(i2c_adr, 0x03, 0x00)
# 移動平均モードを有効
i2c.write_byte_data(i2c_adr, 0x1F, 0x50)
i2c.write_byte_data(i2c_adr, 0x1F, 0x45)
i2c.write_byte_data(i2c_adr, 0x1F, 0x57)
i2c.write_byte_data(i2c_adr, 0x07, 0x20)
i2c.write_byte_data(i2c_adr, 0x1F, 0x00)
##移動平均モードを無効
#i2c.write_byte_data(i2c_adr, 0x1F, 0x50)
#i2c.write_byte_data(i2c_adr, 0x1F, 0x45)
#i2c.write_byte_data(i2c_adr, 0x1F, 0x57)
#i2c.write_byte_data(i2c_adr, 0x07, 0x00)
#i2c.write_byte_data(i2c_adr, 0x1F, 0x00)

plt.ion()
plt.subplots(figsize=(4, 4))

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)       # Number GPIO
signal(SIGPIPE,SIG_DFL)

print("\n 体温測定システムを起動しています。暫くお待ちください...")

####### オプション設定#########
# サーモモニタ表示設定
THERMO_MONITOR_USE = False     # True:USE, False:Not Use
# ログ出力設定
CSV_OUTPUT_USE = False         # True:USE, False:Not Use
# ブザー設定
BUZZER_USE = False             # True:USE, False:Not Use
###############################

# ブザー初期設定
if BUZZER_USE==True:
    BuzzerPin = 17    # GPIO_17
    GPIO.setup(BuzzerPin, GPIO.OUT)
    GPIO.output(BuzzerPin, GPIO.LOW)

# 測距センサー(HC-SR04)初期設定
TRIG = 27
ECHO = 22
GPIO.setup(TRIG,GPIO.OUT)
GPIO.setup(ECHO,GPIO.IN)
GPIO.output(TRIG, GPIO.LOW)

# 測距センサー(HC-SR04)制御
def read_hcsr04():
    #GPIO.output(TRIG, GPIO.LOW)
    #time.sleep(0.1)
    GPIO.output(TRIG, GPIO.HIGH)
    time.sleep(0.000011)
    GPIO.output(TRIG, GPIO.LOW)
    # EchoパルスがHighになる時間
    t0 = time.perf_counter()
    echo_on = time.perf_counter()
    while GPIO.input(ECHO) == 0:
        echo_on = time.perf_counter()
        wait_echo = echo_on - t0
        if wait_echo >= 0.5: # ECHO=0状態を回避
            break
    # EchoパルスがLowになる時間
    t0 = time.perf_counter()
    echo_off = time.perf_counter()
    while GPIO.input(ECHO) == 1:
        echo_off = time.perf_counter()
        wait_echo = echo_off - t0
        if wait_echo >= 0.5: # ECHO=1状態を回避
            break
    # Echoパルスのパルス幅(us)
    echo_pulse_width = (echo_off - echo_on) * 1000000
    # 距離を算出:Distance in cm = echo pulse width in uS/58
    distance = echo_pulse_width / 58
    return distance

################## CSV出力の初期設定 ####################
if CSV_OUTPUT_USE==True:
    # ログ(CSVファイル)設定
    now = datetime.datetime.today()     #現在時刻取得
    hourstr = "_" + now.strftime("%H")  #時刻(時)を文字列化
    minutestr = "_" + now.strftime("%M")   #時刻(分)を文字列化
    filename = "amg8833_temp_" + now.strftime("%Y%m%d") + hourstr + minutestr + ".csv"
    
    with open(filename,'a',newline='') as f:  #csvファイルの生成
        writer = csv.writer(f)
        writer.writerow(["pixels_max2","offset_temp2","thermistor_temp2","distance"])  #1行目:見出し
######################################################

# 日本語表示用のフォント設定
font = cv2.FONT_HERSHEY_SIMPLEX
font_pil = ImageFont.truetype('/usr/share/fonts/truetype/fonts-japanese-gothic.ttf', 20)

# ビデオキャプチャ用のインスタンス生成
cam = cv2.VideoCapture(0) 
# 画像サイズをVGAに合わせて設定(640)
cam.set(3, 640) # set video widht
cam.set(4, 480) # set video height

print("\n 体温測定システムを終了する場合は、このウィンドウ上で[Ctrl+C]を押してください。")

try:
    while True:
        ############################### 体温測定 ########################
        distance = read_hcsr04()
        pixels_array2 = np.array(sensor2.pixels)
        pixels_ave2 = np.average(pixels_array2)
        pixels_max2 = np.amax(pixels_array2[1:6,2:6])   
        pixels_min2 = np.amin(pixels_array2)
        thermistor_temp2 = i2c.read_word_data(i2c_adr, 0xE)
        thermistor_temp2 = thermistor_temp2 * 0.0625
        offset_thrm2 = (-0.6857*thermistor_temp2+27.187)# 補正式
        if distance <= 60:
            offset_thrm2 = offset_thrm2-((60-distance)*0.064)# 補正式(対距離)
        
        offset_temp2 = offset_thrm2
        max_temp2 =  round(pixels_max2 + offset_temp2, 1)   #体温を算出
        print('サーミスタ温度:' + str(thermistor_temp2) + ' ℃')
        print('最大温度:' + str(pixels_max2) + ' ℃')
        print('オフセット値:' + str(offset_temp2) + ' ℃')
        print('体温:' + str(max_temp2) + ' ℃')
        print(pixels_array2)
        print('対象物までの距離:' + str(distance) + ' cm')

        ############################### ブザー設定 ########################
        if BUZZER_USE==True:
            if distance <= 60 and distance >= 25:
            #if distance <= 60:
                GPIO.output(BuzzerPin, GPIO.HIGH)
                time.sleep(0.1)
                GPIO.output(BuzzerPin, GPIO.LOW)
        #########################################################################

        ########################## CSVファイルへのログ出力 ########################
        if CSV_OUTPUT_USE==True:
            if distance <= 60:  
                with open(filename,'a',newline='') as f:  #csvファイルの生成
                    writer = csv.writer(f)
                    #csvファイルへの書き込みデータ
                    data = [pixels_max2,offset_temp2,thermistor_temp2,distance] 
                    #データの書き込み
                    writer.writerow(data)  
                    f.flush()              
        #########################################################################

        ########################## カラーマップ表示 ########################
        if THERMO_MONITOR_USE==True:
            plt.subplot(1,1,1)
            plt.imshow(sensor2.pixels, cmap="jet", interpolation="bicubic",vmin=25,vmax=30,origin='lower') # for AMG8833
            plt.colorbar()
            plt.show
            plt.draw()
            plt.pause(0.01)
            plt.clf()
        #########################################################################

        ############################### カメラ映像表示 ########################
        # 画像の読み込み
        ret, img =cam.read()

        img_pil = Image.fromarray(img)
        draw = ImageDraw.Draw(img_pil)
        
        # 体温測定結果の表示
        if distance <= 60 and distance >= 25:
            if max_temp2 <= 38.0:
                draw.text((20, 50), str(max_temp2)+'℃  体温は正常です', font = font_pil, fill = (255,0,0))
            else:
                draw.text((20, 50), str(max_temp2)+'℃ 体温が高いので、検温してください', font = font_pil, fill = (0,0,255))
                #draw.text((20, 50), '体温は' + str(max_temp)+'℃', font = font_pil, fill = (255,0,0))
        draw.text((200, 80), '顔をこの枠に合わせて近づいてください', font = font_pil, fill = (255,255,255))
        img = np.array(img_pil)
        cv2.rectangle(img, (200,100), (400,350), (255,255,255), 2)
        cv2.imshow('Face-Recognition',img)
        #########################################################################

        # ESCキーで終了
        k = cv2.waitKey(10) &amp; 0xff 
        if k == 27:
            break

except KeyboardInterrupt:
    pass

GPIO.cleanup()
# Do a bit of cleanup
print("\n プログラムが強制終了されました。")
cam.release()
cv2.destr

実行するとこんな感じで表示されます。サーモグラフィを表示すると遅くなるので、運用では非表示にした方が良さそうです。

もうちょっと測定サンプル数を増やして、さらなる向上を目指そうと思います。

何か面白いことを発見したら紹介します。

お気づきの点や、アドバイスなどありましたらお気軽にご連絡ください。

非接触体温計(AMG8833)に関する参考記事です。

測距センサ(HC-SR04)に関する参考記事です。

Follow me!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA