Linebot 画像送信機能の追加
{
"token": "",
"image_file_path": "runs/detect",
"line_bot_channel_access_token": "",
"channel_secret": "",
"line_bot_user_id": ""
}
というように
Config.jsonの内容を追加
import json
import cv2
import configparser
from ultralytics import YOLO
from collections import defaultdict
from datetime import datetime
import os
from line_bot_sender import LineBotSender # LineBotSender をインポート
from inventory_database_module import save_detection_to_db # データベース保存用の関数をインポート
# 設定ファイルの読み込み
config = configparser.ConfigParser()
config.read('config.ini')
# 設定ファイルからモデルパスと画像ディレクトリを取得
model_path = config['Settings']['model_path']
image_directory = config['Settings']['image_directory']
# ラベルマッピングファイルのパス
label_mapping_path = 'label_mapping.json'
# JSONファイルからクラスラベルのマッピングを読み込み
with open(label_mapping_path, 'r', encoding='utf-8') as f:
label_mapping = json.load(f)
# LINE Bot 送信クラスのインスタンスを作成
line_bot = LineBotSender("config.json") # `config.json` を指定
# YOLOv8モデルのロード
model = YOLO(model_path) # 設定ファイルからモデルパスを使用
# 画像ディレクトリ内の全画像ファイルを処理
for image_filename in os.listdir(image_directory):
image_path = os.path.join(image_directory, image_filename)
if os.path.isfile(image_path) and image_path.lower().endswith(('.png', '.jpg', '.jpeg')):
# 画像のロード
image = cv2.imread(image_path)
# 画像の検出
results = model(image, save=True, conf=0.2, iou=0.5)
# 検出結果の取得
detections = results[0] # 最初の結果を取得
classes = detections.boxes.cls
# 検出物体のカウント
object_counts = defaultdict(int)
for cls in classes:
class_label = model.names[int(cls)]
if class_label in label_mapping:
label = label_mapping[class_label]
else:
label = class_label
object_counts[label] += 1
# 検出結果のフィルタリング(1以下のもの)
filtered_object_counts = {label: count for label, count in object_counts.items() if count <= 1}
# フィルタリングされた検出結果のメッセージ生成
message_lines = [f'{label}: {count}個' for label, count in filtered_object_counts.items()]
message = '\n'.join(message_lines)
# 現在の時刻を取得
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
message = f"{message}\n\nMessage sent at: {current_time}"
# 検出結果の表示
for line in message_lines:
print(line)
# LINE Bot にメッセージを送信(フィルタリングされた結果のみ)
if message_lines:
line_bot.send_message(message) # `LineBotSender` を使用
save_detection_to_db(filtered_object_counts) # データベースに検出結果を保存
else:
print("No objects with counts of 1 or less detected in file:", image_filename)
というように
cp count_inventory.py count_inventory_send_bot.py
で
Bot送信のテストのためソースを分ける
変更点は
send_line_notify(message) を
line_bot.send_message(message) に変更
LineBotSender を line_bot_sender.py からインポートして、インスタンスを作成
config.json を LINE Bot の設定情報取得用 に使用
from line_bot_sender import LineBotSender # LineBotSender をインポート
line_bot = LineBotSender("config.json") # `config.json` を指定
# 変更前(LINE Notify を使っていた部分)
# send_line_notify(message)
# 変更後(LINE Bot を使用)
line_bot.send_message(message)
そしてDBへの保存を
# LINE Bot にメッセージを送信(フィルタリングされた結果のみ)
if message_lines:
line_bot.send_message(message) # `LineBotSender` を使用
save_detection_to_db(filtered_object_counts) # データベースに検出結果を保存
へ変更
python count_inventory_send_bot.py
で実行すると
0: 640x512 1 baskulin, 74.1ms Speed: 5.2ms preprocess, 74.1ms inference, 4.6ms postprocess per image at shape (1, 3, 640, 512) Results saved to runs/detect/predict24 バスクリン: 1個
とbot への送信ができたけど
Notifyの時の画像送信機能を作成していなかったので
これを追加する
import requests
import json
import os
from io import BytesIO
from PIL import Image
class LineBotSender:
def __init__(self, config_path):
with open(config_path, 'r') as file:
config = json.load(file)
self.channel_access_token = config.get('line_bot_channel_access_token')
self.channel_secret = config.get('channel_secret')
self.user_id = config.get('line_bot_user_id') # ユーザーID
self.api_endpoint = 'https://api.line.me/v2/bot/message/push' # メッセージ送信用API
self.image_api_endpoint = 'https://api.line.me/v2/bot/message/push' # 画像送信用API
def send_message(self, message):
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.channel_access_token}'
}
data = {
"to": self.user_id,
"messages": [
{
"type": "text",
"text": message
}
]
}
response = requests.post(self.api_endpoint, headers=headers, json=data)
if response.status_code != 200:
raise Exception(f"Error sending message to LINE bot: {response.status_code}, {response.text}")
def send_image(self, image_path, message="画像を送信します"):
headers = {
'Authorization': f'Bearer {self.channel_access_token}'
}
with open(image_path, 'rb') as img_file:
image_data = img_file.read()
image_data = self.resize_image_if_needed(image_data)
files = {'imageFile': BytesIO(image_data)}
files['imageFile'].name = os.path.basename(image_path)
payload = {
"to": self.user_id,
"messages": [
{
"type": "text",
"text": message
},
{
"type": "image",
"originalContentUrl": f"https://your-server.com/images/{os.path.basename(image_path)}",
"previewImageUrl": f"https://your-server.com/images/{os.path.basename(image_path)}"
}
]
}
response = requests.post(self.image_api_endpoint, headers=headers, json=payload, files=files)
if response.status_code != 200:
raise Exception(f"Error sending image to LINE bot: {response.status_code}, {response.text}")
def resize_image_if_needed(self, image_data, max_size=3 * 1024 * 1024):
if len(image_data) > max_size:
image = Image.open(BytesIO(image_data))
new_size = (image.width // 2, image.height // 2)
image = image.resize(new_size, Image.LANCZOS)
output = BytesIO()
image_format = image.format if image.format else 'JPEG'
image.save(output, format=image_format)
return output.getvalue()
return image_data
✅ send_image() メソッドを追加し、画像を送信できるようにした
✅ resize_image_if_needed() で 3MB 超過時にリサイズする処理を追加
✅ originalContentUrl と previewImageUrl は 公開 URL に設定する必要があるため、適宜サーバーにアップロードする処理が必要
しかし
また config.json で “image_file_path”: “runs/detect”,
が設定してあるので
コードを変更する
import requests
import json
import os
from io import BytesIO
from PIL import Image
class LineBotSender:
def __init__(self, config_path):
with open(config_path, 'r') as file:
config = json.load(file)
self.channel_access_token = config.get('line_bot_channel_access_token')
self.channel_secret = config.get('channel_secret')
self.user_id = config.get('line_bot_user_id') # ユーザーID
self.image_file_path = config.get('image_file_path') # YOLOの検出画像ディレクトリ
self.api_endpoint = 'https://api.line.me/v2/bot/message/push'
def send_message(self, message):
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.channel_access_token}'
}
data = {
"to": self.user_id,
"messages": [
{
"type": "text",
"text": message
}
]
}
response = requests.post(self.api_endpoint, headers=headers, json=data)
if response.status_code != 200:
raise Exception(f"Error sending message to LINE bot: {response.status_code}, {response.text}")
def send_image(self, image_path, message="検出された画像を送信します"):
headers = {
'Authorization': f'Bearer {self.channel_access_token}',
'Content-Type': 'application/json'
}
payload = {
"to": self.user_id,
"messages": [
{
"type": "text",
"text": message
},
{
"type": "image",
"originalContentUrl": f"https://your-server.com/images/{os.path.basename(image_path)}",
"previewImageUrl": f"https://your-server.com/images/{os.path.basename(image_path)}"
}
]
}
response = requests.post(self.api_endpoint, headers=headers, json=payload)
if response.status_code != 200:
raise Exception(f"Error sending image to LINE bot: {response.status_code}, {response.text}")
def get_latest_detected_image(self):
""" `image_file_path` ディレクトリから最新の検出画像を取得 """
if not os.path.exists(self.image_file_path):
print("Error: 指定されたディレクトリが存在しません:", self.image_file_path)
return None
images = sorted(
[os.path.join(self.image_file_path, f) for f in os.listdir(self.image_file_path)
if f.lower().endswith(('.png', '.jpg', '.jpeg'))],
key=os.path.getmtime, reverse=True
)
return images[0] if images else None
なお
https://kotovuki.co.jp/archives/17715
によれば
1つの画像の最大ファイルサイズは 10MBらしい
1回のメッセージには吹き出しの上限が5つ
テキストの場合は 1つの吹き出しには最大5000文字まで入力可能
とりあえずサイズを縮小する機能が必要
縦横 50% に縮小
new_size = (image.width // 2, image.height // 2) # 縦横 50% 縮小 image = image.resize(new_size, Image.LANCZOS)
JPEG/PNG を維持して保存
image_format = image.format if image.format else 'JPEG' image.save(output, format=image_format)
リサイズ後の画像データを返す
return output.getvalue()
画像サイズ リサイズ処理
2MB そのまま送信
4MB リサイズして 50% 縮小
8MB リサイズして 50% 縮小(さらに 3MB 超えていれば再縮小)
✅ 3MB 以上の画像は自動で縮小
✅ JPEG/PNG フォーマットを保持
✅ LINE Bot に適したサイズで送信可能
ということで
import requests
import json
import os
from io import BytesIO
from PIL import Image
class LineBotSender:
def __init__(self, config_path):
"""設定ファイルからLINE Botの情報を読み込む"""
with open(config_path, 'r') as file:
config = json.load(file)
self.channel_access_token = config.get('line_bot_channel_access_token')
self.channel_secret = config.get('channel_secret')
self.user_id = config.get('line_bot_user_id') # ユーザーID
self.image_file_path = config.get('image_file_path') # YOLOの検出画像ディレクトリ
self.api_endpoint = 'https://api.line.me/v2/bot/message/push'
def send_message(self, message):
"""テキストメッセージを送信"""
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.channel_access_token}'
}
data = {
"to": self.user_id,
"messages": [
{
"type": "text",
"text": message
}
]
}
response = requests.post(self.api_endpoint, headers=headers, json=data)
if response.status_code != 200:
raise Exception(f"Error sending message to LINE bot: {response.status_code}, {response.text}")
def send_image(self, image_path, message="検出された画像を送信します"):
"""画像を送信(3MB超えた場合はリサイズ)"""
if not os.path.exists(image_path):
print(f"Error: 画像が見つかりません: {image_path}")
return
headers = {
'Authorization': f'Bearer {self.channel_access_token}',
'Content-Type': 'application/json'
}
with open(image_path, 'rb') as img_file:
image_data = img_file.read()
image_data = self.resize_image_if_needed(image_data) # 3MB超えたらリサイズ
# LINE Bot の API 用データを作成
payload = {
"to": self.user_id,
"messages": [
{
"type": "text",
"text": message
},
{
"type": "image",
"originalContentUrl": f"https://your-server.com/images/{os.path.basename(image_path)}",
"previewImageUrl": f"https://your-server.com/images/{os.path.basename(image_path)}"
}
]
}
response = requests.post(self.api_endpoint, headers=headers, json=payload)
if response.status_code != 200:
raise Exception(f"Error sending image to LINE bot: {response.status_code}, {response.text}")
def resize_image_if_needed(self, image_data, max_size=3 * 1024 * 1024):
"""画像が max_size (3MB) を超える場合はリサイズ"""
while len(image_data) > max_size:
image = Image.open(BytesIO(image_data))
new_size = (image.width // 2, image.height // 2) # 縦横 50% 縮小
image = image.resize(new_size, Image.LANCZOS)
output = BytesIO()
image_format = image.format if image.format else 'JPEG'
image.save(output, format=image_format)
image_data = output.getvalue()
return image_data # 3MB 以下になった画像を返す
def get_latest_detected_image(self):
""" `image_file_path` ディレクトリから最新の検出画像を取得 """
if not os.path.exists(self.image_file_path):
print("Error: 指定されたディレクトリが存在しません:", self.image_file_path)
return None
images = sorted(
[os.path.join(self.image_file_path, f) for f in os.listdir(self.image_file_path)
if f.lower().endswith(('.png', '.jpg', '.jpeg'))],
key=os.path.getmtime, reverse=True
)
return images[0] if images else None
if __name__ == "__main__":
sender = LineBotSender("config.json") # 設定ファイルのパスを指定
sender.send_message("こんにちは!") # LINE Bot で送信
latest_image = sender.get_latest_detected_image()
if latest_image:
sender.send_image(latest_image, "最新の検出画像を送信します")
これだけで実行しても画像がつかない
とりあえず
GAS の Webhook エンドポイントを利用し、ローカルの画像を LINE に送信 できるようにする