【ラズパイ×Python】Discord Botで監視カメラシステムを作る

2024/07/30

Discord ラズパイ

  • B!

作りたいもの

DiscordBotで起動したり、停止したりできる動体検知カメラを作りたい。

DiscordBotを使用することで、外出先からでもコントロール可能なカメラが作成できる。

実際のプログラム

Discord Botの機能としては、監視カメラの起動、停止、画像の確認ができれば良いので、まずは監視カメラのプログラムを作成する。

detect.py
  1. #!/usr/bin/python3
  2. import datetime
  3. import cv2 as cv
  4. import os
  5.  
  6. save_dir = '画像の保存先'
  7. fn_suffix = '.jpg'
  8.  
  9. cap = cv.VideoCapture(0)
  10. cap.set(cv.CAP_PROP_FRAME_WIDTH, 1280)
  11. cap.set(cv.CAP_PROP_FRAME_HEIGHT, 720)
  12.  
  13. DELTA_MAX = 255
  14. DOT_TH = 10
  15. MOTHON_FACTOR_TH = 0.10
  16.  
  17. avg = None
  18.  
  19. while True:
  20. ret, frame = cap.read()
  21. motion_detected = False
  22.  
  23. dt_now = datetime.datetime.now()
  24. dt_format_string = dt_now.strftime('%Y-%m-%d %H:%M:%S')
  25. f_name = dt_now.strftime('%Y-%m-%d_%H-%M-%S') + fn_suffix
  26. gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
  27.  
  28. if avg is None:
  29. avg = gray.copy().astype("float")
  30. continue
  31.  
  32. cv.accumulateWeighted(gray, avg, 0.6)
  33. frameDelta = cv.absdiff(gray, cv.convertScaleAbs(avg))
  34. thresh = cv.threshold(frameDelta, DOT_TH, DELTA_MAX, cv.THRESH_BINARY)[1]
  35. motion_factor = thresh.sum() * 1.0 / thresh.size / DELTA_MAX
  36. motion_factor_str = '{:.08f}'.format(motion_factor)
  37.  
  38. cv.putText(frame,dt_format_string,(25,50),cv.FONT_HERSHEY_SIMPLEX, 1.5,(0,0,255), 2)
  39. cv.putText(frame,motion_factor_str,(25,710),cv.FONT_HERSHEY_SIMPLEX, 1.5,(0,0,255), 2)
  40.  
  41. if motion_factor > MOTHON_FACTOR_TH:
  42. motion_detected = True
  43.  
  44. if motion_detected == True:
  45. #print('DETECTED')
  46. cv.imwrite(save_dir + f_name, frame)
  47.  
  48. #画像の最大保存数を決める(50枚)
  49. files = os.listdir(save_dir)
  50. if len(files) >= 51:
  51. #print('REMOVED')
  52. files.sort()
  53. os.remove(save_dir + files[0])
  54.  
  55. k = cv.waitKey(1000)
  56. if k == 27:
  57. break
  58.  
  59. cap.release()
  60. cv.destroyAllWindows()

次にdetect.pyをサービス化する。

念のためdetect.pyとDETECT.serviceには最高権限を与える。

/usr/lib/systemd/system/DETECT.service
  1. [Unit]
  2. Description=Motion Detect Camera
  3.  
  4. [Service]
  5. ExecStart=/home/pi/python/detect.py
  6. Restart=always
  7. Type=simple
  8.  
  9. [Install]
  10. WantedBy=multi-user.target

次に撮影した画像を確認できるプログラムを作成する。

全ての画像をDiscordに送信するのは時間と通信量が消費されすぎるので、一枚のコラージュ画像を作成して送信するようにする。

下記のプログラムは、すでに監視カメラ画像が50枚存在する事を前提にしているため、事前に監視カメラ画像を適当に増やしておく。

collage.py
  1. import os
  2. from PIL import Image, ImageDraw
  3.  
  4. directory = "/nas/motion_detection/" #監視カメラ画像の保存先
  5.  
  6. files = os.listdir(directory)
  7. files.sort()
  8. images = [i for i in files if i.endswith('.jpg') == True]
  9.  
  10. #画像の最大保存数50枚の場合
  11. ##最大横幅(3200)÷(320)=10
  12. ##最大縦幅(900)÷(180)=5
  13. ###コラージュ画像 => 監視カメラの画像(横10枚×縦5枚)
  14. expected_size_collage = (3200, 900) #コラージュ画像のサイズ
  15. expected_size_image = (320, 180) #監視カメラ画像のサイズ(リサイズする)
  16.  
  17. collage = Image.new("RGBA", expected_size_collage, color=(255,255,255,255))
  18.  
  19. file_count = 0
  20. for h in range(0, expected_size_collage[1], expected_size_image[1]):
  21. for w in range(0, expected_size_collage[0], expected_size_image[0]):
  22. file_name = images[file_count]
  23. path = directory + file_name
  24. image = Image.open(path).convert("RGBA")
  25.  
  26. image_width = image.size[0]
  27. image_height = image.size[1]
  28.  
  29. width_factor = image_width / expected_size_image[0]
  30. height_factor = image_height / expected_size_image[1]
  31.  
  32. if width_factor != height_factor:
  33. factor = min(width_factor, height_factor)
  34.  
  35. expected_width = round(factor * expected_size_image[0])
  36. expected_height = round(factor * expected_size_image[1])
  37.  
  38. start_width = round((image_width - expected_width) / 2)
  39. start_height = round((image_height - expected_height) / 2)
  40. end_width = expected_width + round((image_width - expected_width) / 2)
  41. end_height = expected_height + round((image_height - expected_height) / 2)
  42.  
  43. image = image.crop((start_width, start_height, end_width, end_height))
  44.  
  45. image = image.resize(expected_size_image)
  46.  
  47. collage.paste(image, (w, h))
  48. file_count += 1
  49.  
  50. collage.save("コラージュ画像の保存先")

最後にDiscordBotのプログラムを作成する。

bot.py
  1. #!/usr/bin/python3
  2. import discord
  3. from discord.ext import commands
  4. import datetime
  5. import requests
  6. import datetime
  7. import subprocess, shlex
  8.  
  9. webhook = "Discord Webhook URL"
  10.  
  11. intents = discord.Intents.default()
  12. intents.message_content = True
  13.  
  14. bot = commands.Bot(
  15. command_prefix=commands.when_mentioned_or("!"), debug_guilds=[サーバーID], intents=intents
  16. )
  17. client = discord.Client()
  18. channel = チャンネルID
  19.  
  20.  
  21. @bot.event
  22. async def on_ready():
  23. print('[INFO] <' + str(datetime.datetime.now().replace(microsecond=0)) + '> Bot is active')
  24.  
  25. class detect_btn(discord.ui.View):
  26. def __init__(self, timeout=180):
  27. super().__init__(timeout=timeout)
  28.  
  29. #監視カメラを起動する
  30. @discord.ui.button(label="Start", style=discord.ButtonStyle.green)
  31. async def start(self, button: discord.ui.Button, interaction: discord.Interaction):
  32. args = shlex.split("sudo systemctl start DETECT.service")
  33. ret = subprocess.call(args)
  34. await interaction.response.send_message("Start")
  35.  
  36. #監視カメラを停止する
  37. @discord.ui.button(label="Stop", style=discord.ButtonStyle.red)
  38. async def stop(self, button: discord.ui.Button, interaction: discord.Interaction):
  39. args = shlex.split("sudo systemctl stop DETECT.service")
  40. ret = subprocess.call(args)
  41. await interaction.response.send_message("Stop")
  42.  
  43. #保存された画像をコラージュして送信する
  44. @discord.ui.button(label="Callage", style=discord.ButtonStyle.blurple)
  45. async def collage(self, button: discord.ui.Button, interaction: discord.Interaction):
  46. await interaction.response.defer()
  47. args = shlex.split("sudo python3 python/collage.py")
  48. ret = subprocess.call(args)
  49.  
  50. with open('コラージュ画像の保存先', 'rb') as f:
  51. file_bin = f.read()
  52. dht_img = {
  53. "favicon" : ("collage.jpg", file_bin),
  54. }
  55. res = requests.post(webhook, files=dht_img)
  56.  
  57. await interaction.followup.send('Finish')
  58.  
  59.  
  60. @bot.slash_command(name="detect")
  61. async def help_slash(interaction: discord.Interaction):
  62. """motion detection camera"""
  63. await interaction.response.send_message(view=detect_btn())
  64.  
  65. bot.run("TOKEN")

実行結果

Writer

アイコン
Python×Raspi IoTシステム・Bot・ラズパイの記録
  • プログラミング
  • IoT
  • Python
\FOLLOW ME/ 𝕏

Ranking

blogmura_pvcount

Community

Search