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服务即可实现开机自动运行,不需要手动登录启动:
- 给脚本加执行权限:
chmod +x /home/pi/你的脚本实际路径.py - 创建服务文件:
sudo nano /etc/systemd/system/motion-cam.service - 写入以下内容(替换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
- 启用并启动服务:
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
相关产品推荐
相关产品推荐

