You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python HTTP服务启动时如何自动连接localhost触发运动检测循环

问题根因

你的自请求代码无法执行是两个原因导致的:

  • streaming_server.serve_forever()是阻塞方法,调用后程序会一直停在这行监听端口,后面的代码只有服务终止时才会运行,requests.get根本没有执行机会。
  • 就算把requests.get挪到serve_forever()前面也没用:/stream.mjpg是无限推流的长连接,请求发起后会持续阻塞等待响应,此时服务还没完成启动,请求会直接连接失败;就算服务启动了,同线程发请求会占死主线程,外部客户端的访问请求根本没法被处理。

另外你当前的代码架构本身存在性能问题:运动检测逻辑完全绑定在HTTP请求处理流程里,每有一个客户端连接就会新开一套读摄像头、算帧差的循环,Pi Zero性能有限,多连两个客户端就会卡顿,甚至出现摄像头读帧错误。


最优修复方案(无需自请求,性能更高)

把摄像头读取、运动检测逻辑和HTTP推流逻辑解耦:服务启动时就开独立后台线程持续跑检测,全局缓存最新处理好的帧,HTTP接口只负责把缓存的帧发给客户端。这样开机后检测自动运行,不管有没有客户端访问都不影响运动提示打印,还支持多客户端同时访问流不额外消耗性能。

修改后的完整代码如下:

#!/usr/bin/python
import cv2
import http.server
import socketserver
import time
import threading

# 全局变量缓存最新帧和线程锁
latest_frame = None
frame_lock = threading.Lock()
frameinterval = 0

def camera_init():
    cam = cv2.VideoCapture("/dev/video0")
    if not cam.isOpened():
        print("Cannot open camera.")
        raise SystemExit
    cam.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
    cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
    cam.set(cv2.CAP_PROP_FPS, 15)
    return cam

def process(frame1, frame2):
    framediff = cv2.absdiff(frame1, frame2)
    framegray = cv2.cvtColor(framediff,cv2.COLOR_BGR2GRAY)
    frameblur = cv2.GaussianBlur(framegray,(5,5),0)
    threshold = cv2.threshold(frameblur,20,255,cv2.THRESH_BINARY)[1]
    dilated   = cv2.dilate(threshold,None,iterations=10)
    contours  = cv2.findContours(dilated,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)[0]
    motion = False
    for contour in contours:
        if cv2.contourArea(contour) < 10000:
            continue
        (x, y, w, h) = cv2.boundingRect(contour)
        cv2.rectangle(frame2,(x, y),(x+w, y+h),(0,255,0),2)
        motion = True
    return frame2, motion

def detection_loop(cam):
    global latest_frame
    while True:
        frame1 = cam.read()[1]
        frame2 = cam.read()[1]
        if frame1 is None or frame2 is None:
            continue
        processed_frame, motion = process(frame1, frame2)
        if motion:
            print("Motion detected.. " + time.asctime(time.localtime(time.time())))
        # 编码后加锁更新全局帧
        ret, jpg_frame = cv2.imencode(".jpg", processed_frame)
        if ret:
            with frame_lock:
                latest_frame = jpg_frame
        time.sleep(frameinterval)

def webpage():
    return """<html><body>
    <img src="stream.mjpg" width="640" height="480" />
    </body></html>"""

class streaminghandler(http.server.BaseHTTPRequestHandler):
    # 关闭默认访问日志,减少性能消耗
    def log_message(self, format, *args):
        return
    
    def do_GET(self):
        global latest_frame
        if self.path == "/" or self.path == "/index.html":
            content = webpage().encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.send_header("Content-Length", len(content))
            self.end_headers()
            self.wfile.write(content)
        elif self.path == "/stream.mjpg":
            self.send_response(200)
            self.send_header("Content-type", "multipart/x-mixed-replace; boundary=--jpgboundary")
            self.end_headers()
            while True:
                # 等待第一帧就绪
                while latest_frame is None:
                    time.sleep(0.01)
                # 加锁读取最新帧,避免读写冲突
                with frame_lock:
                    frame_to_send = latest_frame
                self.send_header("Content-type", "image/jpeg")
                self.send_header("Content-length", len(frame_to_send))
                self.end_headers()
                self.wfile.write(frame_to_send)
                self.wfile.write(b"\r\n--jpgboundary\r\n")
                time.sleep(frameinterval)
        elif self.path == "/image.jpg":
            while latest_frame is None:
                time.sleep(0.01)
            with frame_lock:
                frame_to_send = latest_frame
            self.send_response(200)
            self.send_header("Content-type", "image/jpeg")
            self.send_header("Content-length", len(frame_to_send))
            self.end_headers()
            self.wfile.write(frame_to_send)

class streamingserver(socketserver.ThreadingMixIn, http.server.HTTPServer):
    # 设置守护线程,客户端断开后自动销毁线程,避免资源泄漏
    daemon_threads = True

if __name__ == "__main__":
    cam = camera_init()
    frameinterval = 1 / cam.get(cv2.CAP_PROP_FPS)
    # 启动后台检测线程,设置为守护线程,主线程退出自动结束
    detection_thread = threading.Thread(target=detection_loop, args=(cam,), daemon=True)
    detection_thread.start()
    print("server started at port 8000, motion detection running")
    streaming_server = streamingserver(("", 8000), streaminghandler)
    try:
        streaming_server.serve_forever()
    except KeyboardInterrupt:
        pass
    finally:
        streaming_server.socket.close()
        cam.release()

快速兼容方案(不改动原有架构,用自请求实现)

如果你不想重构原有逻辑,可以在服务启动前开独立守护线程,等服务端口就绪后发起带流式传输的本地请求,注意必须设置stream=True,否则requests会把无限长度的推流响应全部缓存到内存,很快就会把Pi Zero的内存占满。
只需要修改你原代码的依赖导入和try块部分即可:

import threading
import requests

def self_request():
    # 等待服务端口启动就绪
    time.sleep(1)
    try:
        # stream=True 禁止缓存响应,避免内存溢出
        requests.get("http://127.0.0.1:8000/stream.mjpg", stream=True, timeout=5)
    except:
        pass

try:
    camera = camera()
    frameinterval = 1 / camera.get(cv2.CAP_PROP_FPS)
    streaming_server = streamingserver(("", 8000), streaminghandler)
    print("server started at port 8000")
    # 启动后台线程发自请求
    threading.Thread(target=self_request, daemon=True).start()
    streaming_server.serve_forever()

except Exception as error:
    print(error)

finally:
    streaming_server.socket.close()
    camera.release()

注意:这个方案保留了原有架构的缺陷,多客户端连接时会重复启动检测循环,仅适合临时使用。


开机自启配置

脚本改完后,给树莓派配置systemd服务即可实现开机自动运行,不需要手动登录启动:

  1. 给脚本加执行权限:chmod +x /home/pi/你的脚本实际路径.py
  2. 创建服务文件:sudo nano /etc/systemd/system/motion-cam.service
  3. 写入以下内容(替换ExecStart路径为你实际的脚本路径):
[Unit]
Description=Motion Detection Camera Service
After=network.target

[Service]
User=pi
ExecStart=/usr/bin/python /home/pi/你的脚本实际路径.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
  1. 启用并启动服务:
sudo systemctl daemon-reload
sudo systemctl enable motion-cam.service
sudo systemctl start motion-cam.service

可以用journalctl -u motion-cam.service -f查看运行日志,确认运动检测打印正常。


内容的提问来源于stack exchange,提问作者unformatted

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 03:51:22