现在到了有趣的部分——代码。首先,我们需要收集要分析的视频。我建议准备大约 5 小时的视频素材。您可以随意使用下面的代码,它可以帮助您收集数据并将其存储在名为“videos”的本地文件夹中。
此步骤中您需要拍摄的视频就是您感兴趣的室外区域。
文本 Arduino C Python C++ Java C# Bash Go kotlin JavaScript HTML/XML CSS SQL from picamera2 import Picamera2
from picamera2.encoders import H264Encoder
from picamera2.outputs import FfmpegOutput
from datetime import datetime, timedelta, time as dtime
import os
import time
import sys
def next_window(now):
today = now.date()
start_today = datetime.combine(today, dtime(12, 20, 0))
end_today = datetime.combine(today, dtime(14, 0, 0))
if now < start_today:
return start_today, end_today
elif now < end_today:
return now, end_today
else:
tomorrow = today + timedelta(days=1)
start_tomorrow = datetime.combine(tomorrow, dtime(12, 20, 0))
end_tomorrow = datetime.combine(tomorrow, dtime(14, 0, 0))
return start_tomorrow, end_tomorrow
def main():
out_dir = os.path.join(os.getcwd(), "videos")
os.makedirs(out_dir, exist_ok=True)
now = datetime.now()
start_dt, end_dt = next_window(now)
stamp = start_dt.strftime("%d%m%Y")
filename = f"{stamp}_video.mp4"
out_path = os.path.join(out_dir, filename)
wait_seconds = (start_dt - datetime.now()).total_seconds()
if wait_seconds > 0:
print(f"waiting until {start_dt.strftime('%Y-%m-%d %H:%M:%S')}...")
try:
time.sleep(wait_seconds)
except KeyboardInterrupt:
print("\nInterrupted while waiting. Exiting.")
sys.exit(0)
duration = max(0, (end_dt - datetime.now()).total_seconds())
if duration == 0:
print("[Scheduler] No time left in the window. Exiting.")
return
print(f"Starting capture at {datetime.now().strftime('%H:%M:%S')} "
f"for {int(duration)} seconds (until {end_dt.strftime('%H:%M:%S')}).")
print(f"[Recorder] Output: {out_path}")
picam2 = Picamera2()
video_config = picam2.create_video_configuration(main={"size": (1920, 1080)})
picam2.configure(video_config)
encoder = H264Encoder(bitrate=10_000_000)
output = FfmpegOutput(out_path)
try:
picam2.start_recording(encoder, output)
time.sleep(duration)
except KeyboardInterrupt:
print("\n[Recorder] Interrupted. Stopping recording...")
finally:
try:
picam2.stop_recording()
except Exception:
pass
try:
picam2.close()
except Exception:
pass
print(f"Finished at {datetime.now().strftime('%H:%M:%S')}. Saved: {out_path}")
if __name__ == "__main__":
main()
如果您想找到在检测到鸟类时向您的邮箱发送通知的代码,请使用以下代码。请注意,您需要将 变量替换为相关的电子邮件地址和密码 。
文本 Arduino C Python C++ Java C# Bash Go kotlin JavaScript HTML/XML CSS SQL import argparse
import os
import smtplib
import ssl
import time
from datetime import datetime
from email.message import EmailMessage
from email.utils import formatdate
import cv2
import numpy as np
import pandas as pd
import torch
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 587
GMAIL_SENDER = "youremail@gmail.com"
GMAIL_APP_PASSWORD = "xxxx xxxx xxxx xxxx"
GMAIL_RECIPIENT = "recipient@example.com"
CONFIDENCE_THRESHOLD = 0.35
NMS_IOU_THRESHOLD = 0.45
SNAPSHOT_DIR = "snapshots"
PREVIEW_WINDOW_NAME = "Bird Watch"
def send_email_with_image(subject, body, image_path):
msg = EmailMessage()
msg["Subject"] = subject
msg["From"] = GMAIL_SENDER
msg["To"] = GMAIL_RECIPIENT
msg["Date"] = formatdate(localtime=True)
msg.set_content(body)
if image_path and os.path.exists(image_path):
with open(image_path, "rb") as f:
data = f.read()
ext = os.path.splitext(image_path)[1].lower()
maintype, subtype = ("image", "jpeg") if ext in [".jpg", ".jpeg"] else ("image", "png")
msg.add_attachment(data, maintype=maintype, subtype=subtype, filename=os.path.basename(image_path))
context = ssl.create_default_context()
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
server.ehlo()
server.starttls(context=context)
server.ehlo()
server.login(GMAIL_SENDER, GMAIL_APP_PASSWORD.replace(" ", ""))
server.send_message(msg)
def draw_boxes(frame, detections_df, color=(0, 255, 0)):
for _, row in detections_df.iterrows():
if row["name"] != "bird":
continue
x1, y1, x2, y2 = int(row["xmin"]), int(row["ymin"]), int(row["xmax"]), int(row["ymax"])
conf = float(row["confidence"])
label = f"bird {conf:.2f}"
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
cv2.putText(frame, label, (x1, max(0, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2, cv2.LINE_AA)
return frame
def save_snapshot(frame, folder):
os.makedirs(folder, exist_ok=True)
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
path = os.path.join(folder, f"bird_{ts}.jpg")
cv2.imwrite(path, frame)
return path
def run(video_path, cooldown_s):
model = torch.hub.load("ultralytics/yolov5", "yolov5s", pretrained=True)
model.conf = CONFIDENCE_THRESHOLD
model.iou = NMS_IOU_THRESHOLD
model.classes = None
model.max_det = 300
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise RuntimeError(f"Could not open video file: {video_path}")
last_email_time = 0.0
try:
while True:
ok, frame = cap.read()
if not ok:
break
results = model(frame, size=640)
df = results.pandas().xyxy[0]
bird_df = df[(df["name"] == "bird") & (df["confidence"] >= CONFIDENCE_THRESHOLD)]
display_frame = frame.copy()
if not bird_df.empty:
display_frame = draw_boxes(display_frame, bird_df)
cv2.imshow(PREVIEW_WINDOW_NAME, display_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
now = time.time()
if not bird_df.empty and (now - last_email_time >= cooldown_s):
snapshot_path = save_snapshot(display_frame, SNAPSHOT_DIR)
count = len(bird_df)
top_conf = float(bird_df["confidence"].max())
subject = f"[Bird Alert] {count} bird(s) detected - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
body = f"Detected {count} bird(s) with top confidence {top_conf:.2f}.\nSource: {video_path}\nSnapshot: {os.path.basename(snapshot_path)}\nCooldown: {cooldown_s} seconds\n"
try:
send_email_with_image(subject, body, snapshot_path)
last_email_time = now
except Exception as e:
print(f"Email send failed: {e}")
finally:
cap.release()
cv2.destroyAllWindows()
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--video", type=str, default="input.mp4")
parser.add_argument("--cooldown", type=int, default=180)
args = parser.parse_args()
return args.video, args.cooldown
if __name__ == "__main__":
vid, cd = parse_args()
run(vid, cd)
如果您想使用能够生成特定时间段内鸟类数量图表的代码,请使用以下代码:
文本 Arduino C Python C++ Java C# Bash Go kotlin JavaScript HTML/XML CSS SQL import argparse
import os
import csv
from datetime import datetime
import cv2
import pandas as pd
import torch
import matplotlib.pyplot as plt
CONFIDENCE_THRESHOLD = 0.35
NMS_IOU_THRESHOLD = 0.45
CSV_FILE = "bird_counts.csv"
def run(video_path):
model = torch.hub.load("ultralytics/yolov5", "yolov5s", pretrained=True)
model.conf = CONFIDENCE_THRESHOLD
model.iou = NMS_IOU_THRESHOLD
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise RuntimeError(f"Could not open video file: {video_path}")
fps = cap.get(cv2.CAP_PROP_FPS)
frame_count = 0
with open(CSV_FILE, mode="w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["time_seconds", "bird_count"])
while True:
ok, frame = cap.read()
if not ok:
break
results = model(frame, size=640)
df = results.pandas().xyxy[0]
bird_df = df[(df["name"] == "bird") & (df["confidence"] >= CONFIDENCE_THRESHOLD)]
bird_count = len(bird_df)
time_sec = frame_count / fps
writer.writerow([round(time_sec, 2), bird_count])
display_frame = frame.copy()
for _, row in bird_df.iterrows():
x1, y1, x2, y2 = int(row["xmin"]), int(row["ymin"]), int(row["xmax"]), int(row["ymax"])
conf = float(row["confidence"])
label = f"bird {conf:.2f}"
cv2.rectangle(display_frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(display_frame, label, (x1, max(0, y1 - 6)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2, cv2.LINE_AA)
cv2.imshow("Bird Tracking", display_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
frame_count += 1
cap.release()
cv2.destroyAllWindows()
data = pd.read_csv(CSV_FILE)
plt.figure(figsize=(10, 5))
plt.plot(data["time_seconds"], data["bird_count"], marker="o")
plt.xlabel("Time (seconds)")
plt.ylabel("Number of birds")
plt.title("Birds detected over time")
plt.grid(True)
plt.savefig("bird_graph.png")
plt.show()
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--video", type=str, default="input.mp4")
return parser.parse_args().video
if __name__ == "__main__":
video_file = parse_args()
run(video_file)