• 正文
  • 相关推荐
申请入驻 产业图谱

智能鸟AI:大家都在用树莓派是识人,而我却用树莓派偷偷看鸟!

04/23 14:54
465
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

我创建了一个名为 “智能鸟 AI” 的项目,目的是让观鸟活动变得更加轻松。许多观鸟爱好者常常抱怨,需要花费大量时间等待鸟儿出现在视野中。为此,“智能鸟 AI” 可以检测用户关注区域内是否有鸟类出现,并利用人工智能技术,特别是卷积神经网络(CNN)识别鸟类。一旦检测到鸟儿,系统会立即向用户手机发送 GMAIL 通知,实时提醒用户慢慢靠近观察!这样,观鸟爱好者就可以把大部分时间用在其他活动上,等鸟儿出现时再前去观察记录。

此外,该项目使用了搭载广角相机模块 3 的树莓派 5,镜头可提供 120 度视野,同时集成了 26 TOPS AI 扩展模块,实现实时分析。

每天监测结束后,项目还会生成一张图表,显示每小时出现的鸟类数量。由于鸟类活动规律相对稳定,观鸟爱好者可以参考这张图表,找到鸟类出现最频繁的最佳观测时间。

创建“智能鸟AI”的初衷

观鸟爱好者常常因长时间等待鸟儿出现而感到困扰,同时年轻一代对这项爱好的参与度也不高。经过采访与深入了解,我发现主要原因是他们不愿花费数小时等待鸟儿进入视野。因此,我设计了这套方案,希望让观鸟更高效,并吸引更多人参与。项目还配备了无线可调摄像头,用户可以在不打扰鸟类的情况下,远程控制摄像头进行观察。

所需材料

树莓派5 8GB AI基础套件-英式插头(26 TOPS),包含从树莓派到AI模块以及适配电源的所有组件

树莓派相机模块3广角版

有线USB鼠标

有线USB键盘

显示器(带电源线)或小型触摸屏(3.5英寸或7英寸效果极佳)

Micro-HDMI转HDMI线缆

3D打印机或访问3D打印服务的权限。我使用的是FlashForge Adventurer 5M Pro

UHU粘合胶

计算机

SG90位置伺服电机

步骤1:在MicroSD卡上烧录树莓派操作系统

1. 要安装树莓派操作系统,请将MicroSD卡插入USB适配器,并将其连接到计算机。

2. 从官方树莓派网站下载并安装树莓派烧录软件(Raspberry Pi Imager)。

https://www.raspberrypi.com/software/

3. 打开烧录软件,并按照图像中提供的设置进行配置。

4. 点击“写入”以烧录操作系统。这将需要大约30分钟的时间。

5. 烧录完成后,取出MicroSD卡,将其插入树莓派的MicroSD卡槽中,并接通电源——树莓派将启动并进入操作系统

步骤2:树莓派设置

树莓派本质上是一台配置较低的计算机,但其工作方式与计算机相同。它仍然需要显示器/屏幕和输入命令(键盘和鼠标)。

要正确配置树莓派,请通过USB 3.0接口(蓝色端口)将键盘和鼠标连接到树莓派。然后,将Micro-HDMI线缆插入树莓派,并将HDMI端插入显示器。接通树莓派电源,等待几秒钟,您将进入设置页面。

在此页面上,系统将提示您输入管理员信息,如树莓派用户名和密码、Wi-Fi SSID和密码,以及位置和语言偏好——请填写这些信息!

步骤3:连接摄像头并将AI扩展模块安装到树莓派5上

要设置树莓派5与摄像头和AI扩展模块,请先连接摄像头。轻轻抬起摄像头端口上的黑色塑料部分,然后将摄像头线缆的小端以金属触点朝向正确方向的方式插入。将黑色塑料部分推回原位以锁定线缆。

最好先连接摄像头,因为这样可以提供更多空间,使操作更加简便。之后,将AI扩展模块放在树莓派上方,确保连接器对齐。

使用树莓派底部的短螺丝和顶部的长螺丝固定AI扩展模块。一旦AI扩展模块安装好,如果不先取下扩展模块,就很难取下或调整摄像头。

请确保通过GPIO引脚将伺服电机连接到树莓派。请按照以下方式接线:

棕色线→GND

红色线→5V

黄色线→GPIO 17

步骤4:外壳的3D渲染图

我使用Autodesk Fusion 360创建了这个CAD模型。第一张图片是渲染图,第二张图片可以帮助您了解我使用的具体工具。我还使用Autodesk来了解这个模型的强度,以确保生产质量足够。

要查看外壳的3D渲染图,请查看上述外壳图片。这也有助于您想象项目可能的变化。

https://content.instructables.com/ORIG/FF2/TSZU/MFFQ4UAM/FF2TSZUMFFQ4UAM.stl

步骤5:3D打印安全注意事项

在从打印床上取下3D打印件时,请确保等待5分钟,让打印床冷却下来。这将防止烫伤等潜在危险。

步骤6:3D打印外壳

请下载我在支持文件部分添加的所有.STL文件,并将它们放入切片工具中,如Orca或FlashPrint。对于此步骤,我推荐使用Orca,因为您可以自定义文件的填充密度,这意味着您可以在螺丝区域周围设置为100%填充,而在其他区域降低填充密度。我不建议在所有区域都设置为100%填充,因为这会使模块变得非常重。

查看我在Orca中配置此设置的图片。

打印完成后,请记得移除任何支撑结构。

步骤7:粘合

使用UHU粘合胶将部件粘合在一起。首先,将外壳的顶部粘合到底部。如果您有夹子,此时使用会很方便;否则,使用回形针也可以。

接下来,使用相同的粘合剂将外壳粘合到支架上。我在这里没有使用夹子,但粘合剂的粘合面积很大,可以牢固粘合。UHU胶水在30分钟后开始凝固,但我让它放置了一整夜,以确保其足够坚固。

步骤8:安装

模块的安装非常简单;只需将组件放置在户外视野开阔的地方即可!

步骤9:设置VNC并确保摄像头正常工作

目前,当您连接到树莓派时,需要设置显示器、键盘和鼠标,这非常麻烦。虽然有一些更简单的替代方案,如使用VNC工具。这允许您在没有显示器、键盘或鼠标的情况下访问树莓派——您只需要电源。

要设置此功能,请点击树莓派图标,进入树莓派配置,选择“接口”选项卡,并开启VNC。完成此操作后,转到您的计算机并安装RealVNC。

启用VNC后,您需要找出树莓派的IP地址。为此,您可以通过树莓派用户界面查找或使用IP扫描器。要通过用户界面查找,将鼠标悬停在Wi-Fi图标上,IP地址将显示出来。只需在计算机上的RealVNC中输入这些数字,并输入您的用户名和密码。然后,您将可以通过无头设置完全控制树莓派。如果您希望使用IP扫描器,请搜索IP地址,并在RealVNC中输入该数字。

步骤10:使用代码测试数据

现在是有趣的部分——编写代码。首先,我们需要收集想要分析的视频。我建议为此收集大约5小时的素材。您可以自由使用以下代码,该代码有助于收集数据并将其存储在名为“videos”的本地文件夹中。

您在此步骤中拍摄的视频将只是您感兴趣观看的户外区域。

from picamera2 import Picamera2from picamera2.encoders import H264Encoderfrom picamera2.outputs import FfmpegOutputfrom datetime import datetime, timedelta, time as dtimeimport osimport timeimport sysdef 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_todayelif now < end_today:return now, end_todayelse: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_tomorrowdef 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.")returnprint(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:passtry:picam2.close()except Exception:passprint(f"Finished at {datetime.now().strftime('%H:%M:%S')}. Saved: {out_path}")if __name__ == "__main__":  main()

如果您想查找在检测到鸟儿时向您的电子邮件发送通知的代码,请使用以下代码。注意,您需要将变量替换为相关的电子邮件地址和密码。

import argparseimport osimport smtplibimport sslimport timefrom datetime import datetimefrom email.message import EmailMessagefrom email.utils import formatdateimport cv2import numpy as npimport pandas as pdimport torchSMTP_SERVER = "smtp.gmail.com"SMTP_PORT = 587GMAIL_SENDER = "youremail@gmail.com"GMAIL_APP_PASSWORD = "xxxx xxxx xxxx xxxx"GMAIL_RECIPIENT = "recipient@example.com"CONFIDENCE_THRESHOLD = 0.35NMS_IOU_THRESHOLD = 0.45SNAPSHOT_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 framedef 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 pathdef 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} secondsn"                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.cooldownif __name__ == "__main__":    vid, cd = parse_args()    run(vid, cd)

如果您还想使用提供特定时间鸟类数量有趣图表的代码,请使用以下代码:

import argparseimport osimport csvfrom datetime import datetimeimport cv2import pandas as pdimport torchimport matplotlib.pyplot as pltCONFIDENCE_THRESHOLD = 0.35NMS_IOU_THRESHOLD = 0.45CSV_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().videoif __name__ == "__main__":    video_file = parse_args()    run(video_file)

步骤11:运行程序

要运行代码,只需输入“python3 文件名”,其中“文件名”是您的文件名。在我的情况下是main.py。您看到的分析结果应与附图类似。

主要注意事项:

1.0.XY——这个值(其中XY是一个整数)是模型检测到人物的置信度百分比。

请注意,此脚本还会将数据保存到CSV文件中,我们将在数据收集后分析步骤中使用该文件。

步骤12:数据收集后分析

如果您使用了最后一个示例代码,您将获得一个图表,显示一天中不同时间检测到的鸟类数量。这对于观鸟爱好者来说尤其有价值,因为许多鸟类遵循可预测的群聚模式。通过研究图表,观鸟爱好者可以在第二天类似的时间返回同一区域,并很有可能再次看到相同的鸟类。

步骤13:下一步计划

目前,我的项目已经可以检测区域内是否有鸟类出现,接下来我计划提升模型对鸟类具体种类的识别精度。例如,当用户只希望在检测到鸽子时收到通知,系统便可忽略其他种类的鸟类,仅对目标物种进行提醒。

 

官方网站:https://edatec.cn/zh/cm0

淘宝店铺:https://edatec.taobao.com/

相关推荐