LoginSignup
2
3
はじめての記事投稿

UnitVで雨滴を検知してワイパーをコントロール

Posted at

はじめに

近年高級車には、レインセンサーを使用したオートワイパーが装備されているようです。
レインセンサーは、雨滴を赤外線の屈折により検知しているようですが、今回別のアプローチを用いて作成しましたので紹介いたします。

原理

車のフロントガラスに、UnitVを設置し撮影した画像から、MicroPythone のimg.find_circles()を用いて雨滴の数や雨滴の大きさを、検知することでワイパーを動作させます。
検出画面.gif

MaixPy IDEの記録を使用した動画です。
赤い円は、フロントガラスの雨滴を検出している事を表しています。

 上部の白枠     意味
NUM: 円の検出数
SUMR: 円の大きさの合計
OFF: ON:  リレーの動作
LEVEL 感度(0−2)

回路図

image.png
・シミュレーション
シミュレーション.gif

ソースリスト

# 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は、レンズが接着剤で固定されているものがあるようです。)
レンズ調整.gif
b)設置と調整
 ・UnitVは、L型のアルミ板に両面テープで取り付け、フロントガラスに取り付けます。
 ・UnitVの角度は、下向きに取り付けます。
  (真正面や上向きに取り付けと、木漏れ日等でご動作します。)
 ・USB-TypeCでPCに接続し、MaixPyで雨滴をモニターし焦点距離や感度レベル調整を行います。
  (霧吹きで、水を吹きかけソースリストの動作定義(num,sumr)の値をLEBEL別に調整します。)
 ・ソースリスト編集後、ToolのSave open script to board(boot.py)でSD Cardに書き込みます。
試験.gif

動作動画

雨滴の検出レベルに達した場合、UnitVのLEDを消しリレーに動作信号を渡します。
実写.gif

M5StickVについて

今回、UnitV(OV2640,OV7720 どちらも可能)を使用しましたが、M5StickVでも動作します。
M5StickVは、Groveケーブルでの電源供給は出来ないようで、USB-TypeCを使用する必要があるようです。
また、UnitVのUSB-TypeCから電源供給した場合、Groveケーブルに接続したリレーは動作できませんでした。
M5stickV.gif

さいごに

雨滴をカメラで検出するため、フロントガラスは撥水処理をすることで感度が上がります。
なお、撥水処理しておくこと60Km以上の速度で雨滴が流れ検知をしないため、ワイパーは動作しません。
(そのほうが、視認性が確保され都合が良いです。)
調べた限り、特許権や著作権の侵害に抵触しないようですが、ご利用は自己責任でお願いします。

参考文献

2
3
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
2
3