How to parse mjpeg http stream from ip camera?

后端 未结 5 1691
天命终不由人
天命终不由人 2020-11-27 10:44

Given below is the code written for getting live stream from an IP Camera.

from cv2 import *
from cv2 import cv
import urllib
import numpy as np
k=0
capture=         


        
相关标签:
5条回答
  • 2020-11-27 11:00
    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)   
    

    edit (explanation)

    I just saw that you mention that you have c++ code that is working, if that is the case your camera may work in python as well. The code above manually parses the mjpeg stream without relying on opencv, since in some of my projects the url will not be opened by opencv no matter what I did(c++,python).

    Mjpeg over http is multipart/x-mixed-replace with boundary frame info and jpeg data is just sent in binary. So you don't really need to care about http protocol headers. All jpeg frames start with marker 0xff 0xd8 and end with 0xff 0xd9. So the code above extracts such frames from the http stream and decodes them one by one. like below.

    ...(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)
    

    edit 2 (reading from mjpg file)

    Regarding your question of saving the file, yes the file can be directly saved and reopened using the same method with very small modification. For example you would do curl http://IPCAM > output.mjpg and then change the line stream=urllib.urlopen('http://localhost:8080/frame.mjpg')so that the code becomes this

    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)   
    

    Of course you are saving a lot of redundant http headers, which you might want to strip away. Or if you have extra cpu power, maybe just encode to h264 first. But if the camera is adding some meta data to http header frames such as channel, timestamp, etc. Then it may be useful to keep them.

    edit 3 (tkinter interfacing)

    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()
    
    0 讨论(0)
  • 2020-11-27 11:01

    I don't think the first anwser is fine with other format image data, eg png. So I write the following code, which can handle other type of images

    """
    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
    
    0 讨论(0)
  • 2020-11-27 11:06

    First of all, please be aware that you should first try simply using OpenCV's video capture functions directly, e.g. cv2.VideoCapture('http://localhost:8080/frame.mjpg')!

    This works just fine for me:

    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)
    

    Anyways, here is Zaw Lin's solution ported to OpenCV 3 (only change is cv2.CV_LOAD_IMAGE_COLOR to cv2.IMREAD_COLOR and Python 3 (string vs byte handling changed plus 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)
    
    0 讨论(0)
  • 2020-11-27 11:17

    I had the same problem. The solution without requests or urllib: just add the user and password in the cam address, using VideoCapture, like this:

    E.g.

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

    using IPWebcam for android.

    0 讨论(0)
  • 2020-11-27 11:18

    Here is an answer using the Python 3 requests module instead of urllib.

    The reason for not using urllib is that it cannot correctly interpret a URL like http://user:pass@ipaddress:port

    Adding authentication parameters is more complex in urllib than the requests module.

    Here is a nice, concise solution using the requests module:

    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))
    
    0 讨论(0)
提交回复
热议问题