如何从ip camera解析mjpeg http流?

时间:2014-02-11 12:57:13

标签: python opencv live-streaming ip-camera mjpeg

以下是为从IP摄像机获取实时流而编写的代码。

from cv2 import *
from cv2 import cv
import urllib
import numpy as np
k=0
capture=cv.CaptureFromFile("http://IPADDRESS of the camera/axis-cgi/mjpg/video.cgi")
namedWindow("Display",1)

while True:
    frame=cv.QueryFrame(capture)
    if frame is None:
        print 'Cam not found'
        break
    else:
        cv.ShowImage("Display", frame)
    if k==0x1b:
        print 'Esc. Exiting'
        break

在运行代码时,我得到的输出是:

Cam not found

我哪里错了?另外,为什么帧无?转换有问题吗?

5 个答案:

答案 0 :(得分:75)

import cv2
import urllib 
import numpy as np

stream = urllib.urlopen('http://localhost:8080/frame.mjpg')
bytes = ''
while True:
    bytes += stream.read(1024)
    a = bytes.find('\xff\xd8')
    b = bytes.find('\xff\xd9')
    if a != -1 and b != -1:
        jpg = bytes[a:b+2]
        bytes = bytes[b+2:]
        i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.CV_LOAD_IMAGE_COLOR)
        cv2.imshow('i', i)
        if cv2.waitKey(1) == 27:
            exit(0)   

编辑(解释)

我刚刚看到你提到你有c ++代码正常工作,如果是这样你的相机也可以在python中工作。上面的代码手动解析mjpeg流而不依赖opencv,因为在我的一些项目中,无论我做什么(c ++,python),opencv都不会打开url。

http上的Mjpeg是多部分/ x混合替换边界帧信息,而jpeg数据只是以二进制形式发送。所以你真的不需要关心http协议头。所有jpeg帧都以标记0xff 0xd8开头,以0xff 0xd9结尾。因此,上面的代码从http流中提取这些帧并逐个解码它们。如下。

...(http)
0xff 0xd8      --|
[jpeg data]      |--this part is extracted and decoded
0xff 0xd9      --|
...(http)
0xff 0xd8      --|
[jpeg data]      |--this part is extracted and decoded
0xff 0xd9      --|
...(http)

编辑2(从mjpg文件中读取)

关于保存文件的问题,是的,文件可以直接保存并使用相同的方法重新打开,只需很小的修改。例如,你会做curl http://IPCAM > output.mjpg 然后更改行stream=urllib.urlopen('http://localhost:8080/frame.mjpg'),以便代码成为此

import cv2
import urllib 
import numpy as np

stream = open('output.mjpg', 'rb')
bytes = ''
while True:
    bytes += stream.read(1024)
    a = bytes.find('\xff\xd8')
    b = bytes.find('\xff\xd9')
    if a != -1 and b != -1:
        jpg = bytes[a:b+2]
        bytes = bytes[b+2:]
        i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.CV_LOAD_IMAGE_COLOR)
        cv2.imshow('i', i)
        if cv2.waitKey(1) == 27:
            exit(0)   

当然,您正在保存大量冗余的http标头,您可能希望将其删除。或者如果你有额外的CPU功率,可能只是先编码为h264。但是,如果摄像机正在向http标头帧添加一些元数据,例如频道,时间戳等,那么保留它们可能很有用。

编辑3(tkinter接口)

import cv2
import urllib 
import numpy as np
import Tkinter
from PIL import Image, ImageTk
import threading

root = Tkinter.Tk()
image_label = Tkinter.Label(root)  
image_label.pack()

def cvloop():    
    stream=open('output.mjpg', 'rb')
    bytes = ''
    while True:
        bytes += stream.read(1024)
        a = bytes.find('\xff\xd8')
        b = bytes.find('\xff\xd9')
        if a != -1 and b != -1:
            jpg = bytes[a:b+2]
            bytes = bytes[b+2:]
            i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.CV_LOAD_IMAGE_COLOR)            
            tki = ImageTk.PhotoImage(Image.fromarray(cv2.cvtColor(i, cv2.COLOR_BGR2RGB)))
            image_label.configure(image=tki)                
            image_label._backbuffer_ = tki  #avoid flicker caused by premature gc
            cv2.imshow('i', i)
        if cv2.waitKey(1) == 27:
            exit(0)  

thread = threading.Thread(target=cvloop)
thread.start()
root.mainloop()

答案 1 :(得分:31)

首先,请注意,您应该首先尝试直接使用OpenCV的视频捕获功能 ,例如cv2.VideoCapture('http://localhost:8080/frame.mjpg')

这对我来说很好用:

import cv2
cap = cv2.VideoCapture('http://localhost:8080/frame.mjpg')

while True:
  ret, frame = cap.read()
  cv2.imshow('Video', frame)

  if cv2.waitKey(1) == 27:
    exit(0)

无论如何,这是Zaw Lin的解决方案移植到OpenCV 3(只有cv2.CV_LOAD_IMAGE_COLOR更改为cv2.IMREAD_COLOR和Python 3(字符串与字节处理已更改加上urllib):

import cv2
import urllib.request
import numpy as np

stream = urllib.request.urlopen('http://localhost:8080/frame.mjpg')
bytes = bytes()
while True:
    bytes += stream.read(1024)
    a = bytes.find(b'\xff\xd8')
    b = bytes.find(b'\xff\xd9')
    if a != -1 and b != -1:
        jpg = bytes[a:b+2]
        bytes = bytes[b+2:]
        i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
        cv2.imshow('i', i)
        if cv2.waitKey(1) == 27:
            exit(0)

答案 2 :(得分:10)

以下是使用Python 3 请求模块而非 urllib 的答案。

不使用urllib的原因是它无法正确解释http://user:pass@ipaddress:port等网址

在urllib中添加身份验证参数比请求模块更复杂。

这是一个使用请求模块的简洁解决方案:

import cv2
import requests
import numpy as np

r = requests.get('http://192.168.1.xx/mjpeg.cgi', auth=('user', 'password'), stream=True)
if(r.status_code == 200):
    bytes = bytes()
    for chunk in r.iter_content(chunk_size=1024):
        bytes += chunk
        a = bytes.find(b'\xff\xd8')
        b = bytes.find(b'\xff\xd9')
        if a != -1 and b != -1:
            jpg = bytes[a:b+2]
            bytes = bytes[b+2:]
            i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
            cv2.imshow('i', i)
            if cv2.waitKey(1) == 27:
                exit(0)
else:
    print("Received unexpected status code {}".format(r.status_code))

答案 3 :(得分:1)

我认为第一个Anwser不能与其他格式的图像数据一起使用,例如png。 所以我写了下面的代码,它可以处理其他类型的图像

"""
MJPEG format

Content-Type: multipart/x-mixed-replace; boundary=--BoundaryString
--BoundaryString
Content-type: image/jpg
Content-Length: 12390

... image-data here ...


--BoundaryString
Content-type: image/jpg
Content-Length: 12390

... image-data here ...
"""
import io
import requests
import cv2
import numpy as np


class MjpegReader():
    def __init__(self, url: str):
        self._url = url

    def iter_content(self):
        """
        Raises:
            RuntimeError
        """
        r = requests.get(self._url, stream=True)

        # parse boundary
        content_type = r.headers['content-type']
        index = content_type.rfind("boundary=")
        assert index != 1
        boundary = content_type[index+len("boundary="):] + "\r\n"
        boundary = boundary.encode('utf-8')

        rd = io.BufferedReader(r.raw)
        while True:
            self._skip_to_boundary(rd, boundary)
            length = self._parse_length(rd)
            yield rd.read(length)

    def _parse_length(self, rd) -> int:
        length = 0
        while True:
            line = rd.readline()
            if line == b'\r\n':
                return length
            if line.startswith(b"Content-Length"):
                length = int(line.decode('utf-8').split(": ")[1])
                assert length > 0


    def _skip_to_boundary(self, rd, boundary: bytes):
        for _ in range(10):
            if boundary in rd.readline():
                break
        else:
            raise RuntimeError("Boundary not detected:", boundary)

mr = MjpegReader("http://127.0.0.1/mjpeg.cgi")
for content in mr.iter_content():
    i = cv2.imdecode(np.frombuffer(content, dtype=np.uint8), cv2.IMREAD_COLOR)
    cv2.imshow('i', i)
    if cv2.waitKey(1) == 27:
        break

答案 4 :(得分:-2)

我遇到了同样的问题。 没有请求的解决方案或urllib:只需使用VideoCapture在cam地址中添加用户和密码,如下所示:

E.g。

cv2.VideoCapture( 'http://user:password@XXX.XXX.XXX.XXX/video')

使用IPWebcam for android。