はじめに
近年高級車には、レインセンサーを使用したオートワイパーが装備されているようです。
レインセンサーは、雨滴を赤外線の屈折により検知しているようですが、今回別のアプローチを用いて作成しましたので紹介いたします。
原理
車のフロントガラスに、UnitVを設置し撮影した画像から、MicroPythone のimg.find_circles()を用いて雨滴の数や雨滴の大きさを、検知することでワイパーを動作させます。
MaixPy IDEの記録を使用した動画です。
赤い円は、フロントガラスの雨滴を検出している事を表しています。
上部の白枠 | 意味 |
---|---|
NUM: | 円の検出数 |
SUMR: | 円の大きさの合計 |
OFF: ON: | リレーの動作 |
LEVEL | 感度(0−2) |
回路図
ソースリスト
# Automatic wipers - By: kenji.yamasaki - 土 6月 24 2023 Updete
# ov7740
import sensor
import image
import lcd
import time
import utime
import math
from fpioa_manager import fm
from board import board_info
from Maix import GPIO
from modules import ws2812
lcd.init()
#lcd.rotation(1)
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.run(1)
#### Up Low revers
#sensor.set_vflip()
#### LED
fm.register(8)
class_ws2812 = ws2812(8,100)
b = class_ws2812.set_led(0,(0,0,0))
#### リレーセット(pin 35)
fm.register(35, fm.fpioa.GPIOHS0, force=True)
pin35 = GPIO(GPIO.GPIOHS0, GPIO.OUT)
#### Pin34
fm.register(34, fm.fpioa.GPIOHS1, force=True)
pin34 = GPIO(GPIO.GPIOHS1, GPIO.IN)
# Function PushTime
class Sw_sts:
def __init__(self):
self.min = 5
self.max = 600
def read(self):
if pin34.value() == 0:
CNT = time.ticks_ms()
while(True):
if pin34.value() == 1:
CNT1 = time.ticks_ms()
count = CNT1 - CNT
if (count > self.min) and (count < self.max):
return "S"
if count >= (self.max + 1):
return "L"
#### File
filename = "/sd/config.txt"
#
#filename = "/flash/config.txt"
class File_acc:
def __init__(self):
try:
conf = open(filename, "r") # file ある場合
except:
conf = open(filename, "w") # file ない場合
set_rec = {"ON_OFF": 0, "LEVEL": 3} # ON 0,OFF 1 LEVEL 1-5 Default->3
conf.write(str(set_rec))
conf.close()
# # ON-0,OFF-1
def read(self):
conf = open(filename, "r")
set_rec = eval(conf.read()) #str -> dic 変換
on_off = set_rec["ON_OFF"]
level = set_rec["LEVEL"]
conf.close()
return (on_off,level)
def write(self,on_off,level):
set_rec = {"ON_OFF": on_off, "LEVEL": level}
conf = open(filename, "w")
conf.write(str(set_rec))
conf.close()
class Led_sts:
def __init__(self):
# (32.16,8) # 白 Level 1/32
# (32,0,0) # 赤 Level 1/32 #赤の最大輝度(255,0,0)
# (128,64,0) # 黄 Level 1/32
# (0,64,0) # 緑 Level 1/32 #緑の最大輝度(0,128,0)
# (128,0,32) # 紫 Level 1/32
# (0,0,32) # 青 Level 1/32 #青の最大輝度(0,0,64)
# LEVEL 0 = 緑 LEVEL 1 = 紫 LEVEL 2 = 青
self.typ = {0:(32,16,8),1:(0,64,0),2:(128,0,32),3:(0,0,32)}
def on(self,onoff,level):
if onoff == 1:
a = class_ws2812.set_led(0,self.typ[0])
else:
if level == 0:
a = class_ws2812.set_led(0,self.typ[1])
elif level == 1:
a = class_ws2812.set_led(0,self.typ[2])
elif level == 2:
a = class_ws2812.set_led(0,self.typ[3])
a=class_ws2812.display()
def off(self):
a = class_ws2812.set_led(0,(0,0,0))
a=class_ws2812.display()
class Relay():
def on(self):
pin35.value(1)
def off(self):
pin35.value(0)
# 動作値定義
num = ( 5, 8, 10)
sumr = ( 10, 25, 50)
sw = Sw_sts()
ledsts = Led_sts()
fileacc = File_acc()
relay = Relay()
cnt = 1
onoff = 0
level = 0
# curTim 現在時間(sec)
curTime = 0
# svTim 保存時間(sec) リレーon時の時間と現在時間を比較する。
svTime = 0
while(True):
# file_sts[0] on_off , file_sts[1] level
file_sts = fileacc.read()
onoff = file_sts[0]
level = file_sts[1]
ledsts.on(onoff,level)
sw_time = sw.read()
if file_sts[0] == 1 and sw_time == "S":
onoff = 0
fileacc.write(onoff,level)
ledsts.on(onoff,level)
if file_sts[0] == 0 and sw_time == "S":
level = level + 1
if level == 3:
level = 0
fileacc.write(onoff,level)
ledsts.on(onoff,level)
set_rec["LEVEL"] = level
if sw_time == "L":
onoff = 1
fileacc.write(onoff,level)
ledsts.on(onoff,level)
img = sensor.snapshot()
# ヒストグラム平滑化
img.histeq()
# サークル検出レベル(画像の中から円を探す)
obj = img.find_circles(threshold=2100,r_max = 10)
i = 0
sum_r = 0
# 検出された円の表示と半径の合計
while i < len(obj):
t = obj[i]
x = t[0]
y = t[1]
r = t[2]
color = (255,0,0)
size = 2
img.draw_circle(x,y,r,color,size)
sum_r = sum_r + r
i = i + 1
# 動作値読み込み
conf = open(filename, "r")
set_rec = eval(conf.read()) #str -> dic 変換
conf.close()
l = set_rec["LEVEL"]
# バックホワイト
img.draw_rectangle(20,0,60,10,color=(255,255,255),fill=True )
img.draw_rectangle(90,0,70,10,color=(255,255,255),fill=True )
img.draw_rectangle(180,0,30,10,color=(255,255,255),fill=True )
img.draw_rectangle(240,0,70,10,color=(255,255,255),fill=True )
# ステイタス教示
img.draw_string(20, 0, "NUM: " + str(len(obj)), color=(255,0,0), scale=1)
img.draw_string(90, 0, "SUMR: " + str(sum_r), color=(255,0,0), scale=1)
img.draw_string(240, 0, "LEBEL: " + str(l), color=(255,0,0), scale=1)
lcd.display(img)
# 円の数 = len(obj) , 円の大きさ合計 = sum_r
if (len(obj) > num[l]) and (sum_r > sumr[l]) and (onoff == 0):
curTime = math.floor(utime.ticks_ms() / 100)
if ((curTime - svTime) > 15 ):
print("subTime",(curTime - svTime))
relay.on()
ledsts.off()
img.draw_string(180, 0, "ON", color=(255,0,0), scale=1)
svTime = math.floor(utime.ticks_ms() / 100)
time.sleep(0.3)
relay.off()
ledsts.off()
else:
img.draw_string(180, 0, "OFF", color=(255,0,0), scale=1)
else:
img.draw_string(180, 0, "OFF", color=(255,0,0), scale=1)
動作概要
a) プッシュボタンの機能
・ショートプッシュによる、感度の調整
・UnitVのLEDに設置感度を表示(0: 緑 1:紫 2:青)
・ロングプッシュによる、動作の停止(白)
b) SD Cardへの保存(通電時、記録内容を読み出します。)
・感度の記録
・動作停止の記録
・SD Cardにconfig.txtが無い場合、初期値でFILEを作成
c) サークル検出レベル
・img.find_circlesで、しきい値と最大サイズ(r_max)を定義(試行錯誤で調整)
試験調整
a)フロントガラスとの焦点距離調整
・フロントガラスの雨滴に焦点を合わせる為、分解しレンズをフリーにします。
(UnitVは、レンズが接着剤で固定されているものがあるようです。)
b)設置と調整
・UnitVは、L型のアルミ板に両面テープで取り付け、フロントガラスに取り付けます。
・UnitVの角度は、下向きに取り付けます。
(真正面や上向きに取り付けと、木漏れ日等でご動作します。)
・USB-TypeCでPCに接続し、MaixPyで雨滴をモニターし焦点距離や感度レベル調整を行います。
(霧吹きで、水を吹きかけソースリストの動作定義(num,sumr)の値をLEBEL別に調整します。)
・ソースリスト編集後、ToolのSave open script to board(boot.py)でSD Cardに書き込みます。
動作動画
雨滴の検出レベルに達した場合、UnitVのLEDを消しリレーに動作信号を渡します。
M5StickVについて
今回、UnitV(OV2640,OV7720 どちらも可能)を使用しましたが、M5StickVでも動作します。
M5StickVは、Groveケーブルでの電源供給は出来ないようで、USB-TypeCを使用する必要があるようです。
また、UnitVのUSB-TypeCから電源供給した場合、Groveケーブルに接続したリレーは動作できませんでした。
さいごに
雨滴をカメラで検出するため、フロントガラスは撥水処理をすることで感度が上がります。
なお、撥水処理しておくこと60Km以上の速度で雨滴が流れ検知をしないため、ワイパーは動作しません。
(そのほうが、視認性が確保され都合が良いです。)
調べた限り、特許権や著作権の侵害に抵触しないようですが、ご利用は自己責任でお願いします。
参考文献