
我的行车记录仪在挡风玻璃上躺了大半年除了防碰瓷基本没干过正事。直到我想把它改造成一台可编程的常开摄像头用来做路况采集、目标检测甚至把画面上传到家里看实时路况才发现网上绝大部分资料都是讲手机App怎么连真正用python调取海康威视行车记录仪摄像头视频流的完整思路少得可怜。折腾了两周踩了不少坑之后我把整条路线摸透了不装厂商App不碰浏览器插件直接用RTSP协议把记录仪的视频流接进Python再用OpenCV或FFmpeg处理。这篇文章就是我的完整实操记录内容覆盖从IP发现、RTSP地址拼接到OpenCV拉流、断流重连再到RTMP推流和帧级应用适合想把自己的行车记录仪或海康监控摄像头变成智能摄像头、做二次开发的Python开发者参考。1. 为什么绕开App和浏览器插件直接走RTSP协议1.1 视频流的三种入口对比先看一个大家最容易困惑的问题行车记录仪明明有App也有网页管理界面为什么非要折腾RTSP我把几种入口放在一起对比过结论非常明显。入口方式可编程性稳定性主要坑厂商App / 手机无线连接基本为零私有协议还行只能看不能调无法接Python浏览器插件ActiveX等受限只能在浏览器里跑差Win10下Edge、Chrome都加载不了海康插件RTSP标准协议好OpenCV、FFmpeg、VLC都能读取决于链路质量地址路径各家有差异需要自己探测很多同学在网上搜“海康威视 视频流 python”搜出来的多半是监控摄像头的教程但把方法搬到行车记录仪上会翻车。原因在于行车记录仪更像一个封闭的嵌入式设备没有官方SDKApp也不开放接口浏览器插件方案更是直接死在ActiveX上——新版Edge和Chrome都不支持这种老掉牙的插件Win10下面我试了一晚上也没让插件正常跑起来。但记录仪再封闭它也得给手机App提供预览画面所以底层一定有一条视频流通道。绝大多数记录仪走的正是RTSP协议。RTSP是标准协议只要地址正确VLC、FFmpeg、OpenCV都能直接读Python自然也能拿到流。1.2 海康系RTSP地址的命名规则海康威视监控摄像头的标准RTSP地址这样拼rtsp://用户名:密码IP地址:端口/Streaming/Channels/通道号码流标识码流标识的规律是101第1通道主码流分辨率最高102第1通道子码流分辨率较低201第2通道主码流多目、多通道设备才有主码流和子码流的选择在后面会反复提到先记住一条经验调试阶段一律先用102子码流原因我放到第4章详细讲。行车记录仪不一定遵守这套命名规则。我实际遇到过几种路径形态rtsp://192.168.1.1:8554/live算是最常见的还有rtsp://192.168.1.1:554/Streaming/Channels/101这种海康方案风格也见过老式IPC风格的rtsp://192.168.1.254:554/h264/ch1/main/av_stream。这告诉我一个教训不要死背一个地址不同固件的记录仪差异很大必须拿工具去探测。1.3 用Python处理视频流的路线Python端拿到RTSP流之后常用的处理方式有三条OpenCV的VideoCapture直接读帧适合做图像处理、AI检测FFmpeg推流或转码适合直播、录播、跨网传输GStreamer管道Linux平台适合低延迟场景我最终在项目里用的是“OpenCV读帧 FFmpeg推流”的组合这也是大多数人最容易上手的方案。整体思路是RTSP协议负责把视频流从记录仪拉到你的电脑或板子上Python在应用层负责消费这些视频帧具体是用来看、是拿来分析、还是再转发出去完全由你的业务决定。2. 连接记录仪的第一步找到IP、端口和正确的RTSP路径2.1 记录仪的两种组网方式行车记录仪和你的开发机连起来有两种常见拓扑。第一种是AP模式也就是记录仪自己开WiFi热点。这种情况下记录仪自身IP通常是192.168.1.1或192.168.1.254你的电脑连上这个热点后本机IP一般会被分配成192.168.1.x。多数记录仪出厂默认就是这种模式直接搜热点连上去就行。第二种是STA模式记录仪像手机一样连你家里的路由器。这时候记录仪在局域网里被路由器分配了一个IP你没法直接猜到要去路由器后台看DHCP客户端列表才能找到。我在自己路由器后台里就见过记录仪的设备名有的叫DVR有的直接显示芯片方案型号需要逐个确认。2.2 探测端口和路径默认RTSP端口是554但记录仪经常不按套路出牌用8554、8080乃至55000端口的都有。最省事的探测方法是用ffprobeFFmpeg自带这个工具ffprobe rtsp://192.168.1.1:8554/live如果路径和端口都对ffprobe会输出视频流的分辨率、编码格式、帧率等信息如果不对会提示404 Not Found或者Connection refused。也可以用VLC的“打开网络串流”界面一个个试VLC的错误提示比ffprobe更友好。为了批量确认我写过一个简单的Python脚本用socket去扫记录仪IP的常见端口是否开放再组合常见路径去试。这个脚本在调试不同品牌记录仪时特别有用推荐大家也维护一个自己的“常见RTSP路径字典”因为不同固件的命名习惯真的差很多。字典里至少要有/live、/live/ch0、/Streaming/Channels/101、/Streaming/Channels/102、/h264/ch1/main/av_stream这几类。2.3 账号密码先搞清楚设备是谁的RTSP地址里的用户名密码不是乱填的。海康设备通常有默认账号老款是admin但现在的新固件在初始化时就会强制你设置密码行车记录仪有些固件的RTSP通道干脆不鉴权只要知道IP和路径就能看流这类设备在局域网内尤其要注意暴露风险。这里只讨论你自己车上那台记录仪。如果你拿到的是别人的设备或者公共摄像头务必先获得授权不要尝试绕过任何鉴权机制。安全上多说一句记录仪直接暴露在公网是非常危险的事情我建议只在局域网内使用如果要远程访问也得走RTMP服务器加鉴权而不是直接把RTSP端口映射到公网。2.4 可以抄作业的地址候选清单场景IP端口路径记录仪WiFi热点192.168.1.18554/live记录仪WiFi热点192.168.1.1554/Streaming/Channels/102海康摄像机局域网分配IP554/Streaming/Channels/101老式IPC局域网分配IP554/h264/ch1/main/av_stream表格里的形态都是常见配置不代表你的设备一定在其中。真正靠谱的流程是先确定组网方式拿到IP再用ffprobe逐个试端口和路径最后把能连通的那个地址固定下来写进代码。3. 用OpenCV拉流10行代码看画面的原理与参数调优3.1 最简Demo先把最简单的代码放出来import cv2 url rtsp://admin:你的密码192.168.1.1:8554/live cap cv2.VideoCapture(url) if not cap.isOpened(): print(连接失败先检查IP、端口、路径和用户名密码) exit() while True: ret, frame cap.read() if not ret: print(读帧失败) break cv2.imshow(dashcam, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()实测下来只要地址正确这段代码就能弹出预览窗口。但“能弹出”和“好用”之间隔着天壤之别。如果你发现程序一直卡在cv2.VideoCapture(url)出不来说明OpenCV在底层已经阻塞住了这不是代码问题是RTSP链路的问题后面会有专门的处理方式。3.2 OpenCV拉流的两大硬伤我自己在车上实测OpenCV直连RTSP有两个特别难受的地方。第一默认缓冲区太大。OpenCV的FFmpeg后端会自动缓存不少帧表现出来就是画面延迟非常严重。我原本以为只有几百毫秒结果实际操作下来能到3到5秒想在车上做实时判断根本没法用。缓解方法是把缓冲区压到最小cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)这个参数在不同版本的OpenCV上不总是生效但设了总比不设好。另外几个值得设置的参数包括cap.set(cv2.CAP_PROP_FPS, 30) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)把分辨率往下压在弱网环境下能明显减少卡顿。第二没有有效的超时机制。如果中途WiFi断一下cap.read()可能会卡住很久都不返回程序就像死了一样。OpenCV自身没有提供像样的超时配置这也是为什么我在第4章会强烈建议你换FFmpeg方案。3.3 子码流是调试期的保命符行车记录仪的2K/4K主码流码率很高车载WiFi往往是2.4GHz在车里这种电磁环境下很难稳定承载。所以在调试阶段建议优先用子码流102或者设备的低分辨率流。等整条链路验证通了、你要做正式采集了再上主码流。这不是偷懒而是为了把“网络问题”和“代码问题”分开排查。如果子码流都拉不动那明显是网络链路有问题先解决WiFi如果子码流很流畅、主码流才卡那才是带宽不够再去想降低码率或者换有线连接。4. 断流、卡死、延迟车载环境下的稳定性改造这是我在这个项目里花时间最多的地方直接从问题清单开始。现象直接原因我的处理程序无响应OpenCV阻塞在read上换FFmpeg拉流管道或加重连机制画面延迟越来越大缓冲区积压压低BUFFERSIZE、用低码流花屏、马赛克UDP丢包强制TCP传输看几分钟就断开RTSP会话超时或供电波动自动重连定期喂帧4.1 强制TCP传输RTSP流默认可能走UDPUDP在丢包环境下就是花屏和掉帧。TCP虽然延迟稍微高一点但可靠得多。OpenCV 4.x的某些版本里支持设置传输方式cap.set(cv2.CAP_PROP_RTSP_TRANSPORT, cv2.CAP_PROP_RTSP_TRANSPORT_TCP)如果你的OpenCV版本不认这个属性它确实在不同的编译选项下表现不一致那就直接用FFmpeg把流拉成一个本地管道再用Python读。这个思路很关键——与其和OpenCV的RTSP后端搏斗不如把“拉流”这件事交给FFmpegPython只负责“吃帧”。import subprocess import cv2 import numpy as np cmd [ ffmpeg, -rtsp_transport, tcp, -i, rtsp://admin:你的密码192.168.1.1:8554/live, -vf, fps30, -f, rawvideo, -pix_fmt, bgr24, -an, pipe:1 ] proc subprocess.Popen(cmd, stdoutsubprocess.PIPE, stderrsubprocess.DEVNULL) width, height 1280, 720 frame_size width * height * 3 while True: raw proc.stdout.read(frame_size) if len(raw) ! frame_size: break frame np.frombuffer(raw, np.uint8).reshape(height, width, 3) # 在这里处理 frame cv2.imshow(ffmpeg pipe, frame) if cv2.waitKey(1) 0xFF ord(q): break proc.terminate() cv2.destroyAllWindows()这个方案的优点TCP可靠、fps可控、超时更容易在管道层感知。缺点宽高写死了不同分辨率要改参数或者自己从ffprobe里查实际分辨率再填进去。4.2 自动重连机制车载环境里WiFi瞬断是常态记录仪在汽车熄火后还可能断电重启。最可靠的方案是把“连接”和“读帧”包成一个循环import cv2 import time url rtsp://admin:你的密码192.168.1.1:8554/live def connect(): cap cv2.VideoCapture(url) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) return cap cap connect() while True: ret, frame cap.read() if not ret: print(连接丢失等待重连...) cap.release() time.sleep(2) cap connect() continue # 正常处理重点是把cap.release()和重新VideoCapture放进循环里不要一断就整个程序退出。我在实际使用中还会加一个最大重试次数和日志输出这样记录仪异常重启后脚本自己能恢复不用每次上车都去手动启动。4.3 用-rtsp_transport tcp和-stimeout做兜底如果你用的是FFmpeg命令行可以在参数里加超时配置ffmpeg -rtsp_transport tcp -stimeout 10000000 -i rtsp://... -f flv rtmp://...-stimeout的单位是微秒10000000就是10秒。超过10秒连不上FFmpeg直接报错退出方便你的Python侧感知并重启任务。这是解决“卡死到天荒地老”的最粗暴有效的手段。我后来把OpenCV直连方案彻底淘汰掉了统一改成FFmpeg管道或者FFmpeg子进程稳定性提升了不止一个档次。4.4 带宽估算与码流选择最后补一个判断依据。H.264编码的1080p画面码率大约在4到8Mbps2K分辨率能到10Mbps以上车载WiFi在这种压力下很容易扛不住。如果用的是子码流102很多记录仪会把码率压到2Mbps左右画面清晰度够用稳定性却好很多。我的经验先保证流畅再追求清晰不要一开始就追求主码流否则你会花大量时间在排网络上误以为代码写错了。5. 把视频流推给RTMP服务器直播与异地查看5.1 为什么非要把RTSP转成RTMPRTSP协议在局域网内好用但放到公网直播场景就尴尬了需要客户端支持RTSP防火墙对RTSP的穿透也不太友好。RTMP是现在直播平台和自建流媒体服务器普遍支持的协议你的记录仪加上这个Python进程等于一个小型直播编码器可以把行车画面推给家里的Nginx-RTMP服务器或者私有直播平台实现真正的异地查看。5.2 一条实测能跑的推流命令直接给结论。最省CPU的方式是只转封装不转编码ffmpeg -rtsp_transport tcp -i rtsp://admin:你的密码192.168.1.1:8554/live \ -c:v copy -f flv rtmp://192.168.1.100:1935/live/car01-c:v copy说白了就是视频编码不动只把容器从PS/TS换成FLVCPU几乎不消耗。如果你的RTMP服务器或者播放端需要低延迟可以再加ffmpeg -rtsp_transport tcp -i rtsp://... \ -c:v libx264 -preset ultrafast -tune zerolatency \ -f flv rtmp://...ultrafast是牺牲一点压缩率换速度zerolatency是消除编码延迟适合实时画面。实测局域网内RTSP到RTMP中转延迟大约1秒以内公网就看上行带宽了。如果上行带宽不够就得降低分辨率或者帧率再编码。5.3 Python进程怎么管理FFmpeg在Python里用subprocess.Popen拉起FFmpeg是最灵活的方式import subprocess import time def start_push(rtsp_url, rtmp_url): cmd [ ffmpeg, -rtsp_transport, tcp, -stimeout, 10000000, -i, rtsp_url, -c:v, copy, -f, flv, rtmp_url ] return subprocess.Popen(cmd, stdoutsubprocess.DEVNULL, stderrsubprocess.DEVNULL) proc start_push(url, rtmp_url) while True: if proc.poll() is not None: print(FFmpeg挂了重启) proc start_push(url, rtmp_url) time.sleep(5)这里的关键是proc.poll()。FFmpeg进程一旦退出返回值就不是NonePython侧就知道该重启了。我在车里测试时因为记录仪WiFi偶尔抖动FFmpeg不到半小时退过一次加了自动重启之后整晚都没断过。要注意stderr别直接丢进黑屏调试阶段还是应该打到日志文件里否则FFmpeg为什么退出你根本不知道。5.4 录播一体的思路如果你既要直播又要留档可以开两个FFmpeg实例一个推RTMP一个写本地文件。写文件的命令只需要把输出换成-c:v copy -f mp4 /data/record_$(date %s).mp4录播一体时注意磁盘写入速度回应很多人在问的2K记录仪兼容U3卡的问题U3卡主要解决的是记录仪自己写卡的带宽Python侧转存的是另外一份数据存储介质跟不上照样会掉帧。建议把转存文件写到固态硬盘或者大缓存U盘别跟记录仪抢同一张卡。6. 从帧出发截图、检测、多路同屏等实际玩法6.1 定时截图和事件片段OpenCV拿到帧之后第一件能落地的事就是定时截图。我的车载项目里就有这样一个逻辑每5秒存一张带时间戳的JPEG当作行车日志。代码非常简单timestamp time.strftime(%Y%m%d_%H%M%S) cv2.imwrite(f/data/frames/{timestamp}.jpg, frame)如果要做“碰撞前后自动保存”建议结合记录仪自己的G-Sensor事件文件而不是在Python侧做震动检测因为Python端拿不到传感器数据强行用画面抖动来判断碰撞误报率会高到离谱。6.2 把帧喂给目标检测模型这一步是所有AI摄像头应用的落点。OpenCV读出来的frame就是一个numpy数组如果你想做火灾检测、车辆检测、行人检测直接把这张帧丢给YOLO或者你常用的检测模型就行。我搜到有人用手机摄像头做YOLO火灾实时监控其实思路与此一模一样换成海康记录仪的视频流也只是换一个帧来源而已。车载画面有个特点抖动非常厉害。直接对全画面做运动检测结果会被路面的颠簸干扰到怀疑人生。建议先设定一个ROI区域比如只检测前挡风玻璃固定区域内的目标或者先用简单的方式判断“画面是否发生了整体位移”再做具体的目标检测。6.3 多路视频拉流与一屏总览再扩展一步。如果你手头不止一台摄像头想做多路同屏做法是每个摄像头开一个线程拉流把最新帧放到一个字典里主线程负责缩放、拼接、显示import threading import cv2 import numpy as np urls { front: rtsp://.../Streaming/Channels/102, rear: rtsp://.../Streaming/Channels/102, } frames {} lock threading.Lock() def worker(name, url): cap cv2.VideoCapture(url) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) while True: ret, frame cap.read() if not ret: continue frame cv2.resize(frame, (640, 360)) with lock: frames[name] frame for name, url in urls.items(): threading.Thread(targetworker, args(name, url), daemonTrue).start() while True: with lock: if len(frames) len(urls): merged np.hstack([frames[n] for n in urls]) cv2.imshow(all cams, merged) if cv2.waitKey(1) 0xFF ord(q): break这个思路可以直接迁移到“多部同视角枪机视频流拼接系统实现一屏总览”的场景。要点是线程只负责取帧显示和AI分析都放主线程避免GIL把多路拉流搞成一团乱麻。如果摄像头数量多到CPU吃紧可以考虑把每路的分辨率再压一压。6.4 别指望RTSP里有GPS信息行车记录仪的水印上经常有时间、速度、经纬度但这些东西很多是厂商在编码时烧录进视频画面的或者存在记录仪自己的私有文件里。RTSP视频流本身通常只包含视频编码数据GPS信息你是拿不到的。如果要做时间轴分析就用Python侧的机器时间打时间戳别去解析画面水印那条路太痛苦。我一开始试图从视频流里解析经纬度折腾半天发现毫无头绪后来老老实实用手机GPS做外部数据源问题立刻解决了。7. 关于记录仪和摄像头的那几个高频坑7.1 浏览器加载不了海康插件别装了“win10浏览器加载不了海康威视的插件”是一个出现频率极高的搜索词。说实话这个问题在当前环境里基本无解因为新版Chrome、Edge已经把ActiveX这条老路封死了海康老式网页插件在Win10上属于被淘汰的方案。如果你想在电脑上看画面与其折腾插件不如直接写一个上面的Python小程序用OpenCV弹窗预览或者干脆用VLC打开RTSP地址。换个思路反而什么问题都没有了。7.2 记录仪定制安卓系统隐藏了原生设置有人问记录仪定制化安卓系统隐藏了原生设置能不能开开发者模式。我拆过一台类似的设备它内部确实是安卓但厂商把设置入口藏得很深通过常规的连点版本号方式基本进不去开发者模式意味着你无法轻松通过ADB做系统级调试。好消息是这根本不重要视频流是从编码芯片直接出来的不经过安卓上层所以Android系统再怎么封闭RTSP取流都不受影响。你只需要在局域网层面和它打交道不用去碰系统。7.3 GB28181是另一条路线别混着用还有人在搜“海康威视gb28181接入平台开源”。GB28181是一种用于设备接入统一监管视频平台的标准协议走SIP信令和RTSP完全是两套体系。如果你的目标只是用Python读视频流、做图像处理、推个直播RTSP就够了完全不需要碰GB28181。只有当你要把一堆设备统一接入某个监管类视频平台、需要平台主动拉流或做级联的时候才需要考虑GB28181那种场景下也建议直接用现成的开源网关做协议转换别自己用Python去实现SIP和PS封包工程量巨大且没必要。7.4 周边硬件坑WiFi频段、存储卡与供电行车记录仪最常见的问题是WiFi不稳定。很多记录仪只支持2.4GHz在车里这种密封环境下干扰源又多所以前面反复强调用TCP传输、低码流、自动重连。另外2K/4K高码率连续录制时对存储卡写入要求很高这就是“2k行车记录仪兼容u3卡吗”这类问题的来源——U3卡能保证记录仪自身不掉帧但如果你在Python侧同时转存建议用独立的存储设备别跟记录仪抢读写。再有一个容易被忽略的坑记录仪在汽车熄火后可能断电你的Python拉流进程必须在记录仪重新上电后能自己恢复这就是第4章重连循环存在的意义。我后来习惯把程序写成systemd服务或者计划任务自启上车通电后什么都不用管脚本自己会把画面拉起来。我自己用下来最顺手的组合是FFmpeg负责拉流和格式转换OpenCV只负责吃帧做分析不要指望OpenCV的RTSP后端处理所有异常。调试记录仪视频流永远从子码流开始永远把TCP传输放在第一位永远给程序留一条重连的路。最后提醒一句行车记录仪画面里可能有路人、车牌等信息本地做分析没问题如果要传到公网或者第三方平台先做好脱敏和访问控制别把隐私数据裸奔在路上。