30

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=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

On running the code the output that I am getting is:

Cam not found

Where am I going wrong? Also, why is frame None here? Is there some problem with the conversion?

Zaw Lin
  • 5,629
  • 1
  • 23
  • 41
praxmon
  • 5,009
  • 22
  • 74
  • 121
  • Is that CGI script returning the video stream or a HTML page for browser display? – Andris Feb 11 '14 at 14:53
  • @Andris It returns a video stream, I have tried playing it using VLC and it works. – praxmon Feb 12 '14 at 03:27
  • I have no IP camera but [others](http://www.computer-vision-software.com/blog/2009/04/how-to-get-mjpeg-stream-from-axis-ip-cameras-axis-211m-and-axis-214-ptz-as-camera-device-in-opencv-using-directshow/) have fought with Axis cameras a lot in 2009. Apart from that ["mjpg" at the end of the URL](http://stackoverflow.com/a/15462399/501814) may help. – Andris Feb 12 '14 at 08:41
  • As I see you have [tried different aproaches](http://stackoverflow.com/questions/21721813/ip-camera-python-error) as well and fixed the mjpg already. :) One last guess is using gstreamer between OpenCV and the IP camera. – Andris Feb 12 '14 at 09:07
  • @Andris I saw that, see my comment on that answer. – praxmon Feb 12 '14 at 09:19
  • What is gstreamer? And how do I use it? Any tutorials/guides/questions on it? In the meantime I'll try installing it on windows. – praxmon Feb 12 '14 at 09:26
  • Please check this link: http://xuv.be/OpenCV-GStreamer-Camera-over-IP.html (although it's Processing, not python). – Andris Feb 12 '14 at 11:25
  • @Andris Ah, I tried processing, the problem is it does not DB support and also, it can primarily be used for Data visualization and I cannot use it to build a complete application. As in cannot add button, text boxes etc. etc. Although I guess libraries must be present but I could not find any which could work on all three (windows, linux and iOS). If you do know then tell me, I shall try. – praxmon Feb 12 '14 at 11:35
  • I'm using VideoCapture("http://url/to/cam") and that works just fine. – Kasisnu Aug 21 '14 at 17:47

5 Answers5

87
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()
Zaw Lin
  • 5,629
  • 1
  • 23
  • 41
  • bytes is a growing queue that is slowly consumed by the parsing loop, the stream.read(16384) read 16384 bytes of data from http stream and add it to bytes,which will be shortened if a valid jpeg frame is found. the read buffer should be smaller than the smallest jpeg frame size, otherwise, there might be problems. – Zaw Lin Feb 18 '14 at 04:38
  • btw, i have also needed to do the same in c++, which is posted here(http://stackoverflow.com/questions/20899511/opencv-load-image-video-from-stdin). idea is the same, but implementation there is more robust than python and is production tested. might be useful for you if you want to maintain same behaviour for both of your codebase – Zaw Lin Feb 18 '14 at 04:41
  • Ah, I was just going to ask you for a similar C++ code. :) So another question, the bytes that are read here from the stream contain images in binary format? If so, can I save this stream of data? Then I am able to view it later? – praxmon Feb 18 '14 at 04:52
  • Do you know why it would lag? The more the duration of the video the more the lag? – praxmon Feb 20 '14 at 04:45
  • Hmm...there shouldnt be any lag. Are you talking about the python version or c++ version? Can you describe in more details? – Zaw Lin Feb 20 '14 at 10:22
  • Ah, I figured it out. Although I don't know why but the python version was lagging more and more when I was running it. Then I changed the size from 16384 to 1024 and it is running fine. Don't know why it was lagging and why its working completely fine now. – praxmon Feb 20 '14 at 10:24
  • i think it can slightly increased cpu usage. the read buffer shouldnt be too small since, `find` operation if repeatedly done too many times can be quite expensive. to be honest, python version was a lazy(but easy) implementation just to quickly test for experimental work. maybe if i have some time during this weekend, i can update with a slightly better method. – Zaw Lin Feb 20 '14 at 10:44
  • Awaiting the updated answer if you do update. :) Thanks again! – praxmon Feb 20 '14 at 10:47
  • Okay another question, what do you use `jpg = bytes[a:b+2] bytes= bytes[b+2:]` for? – praxmon Feb 25 '14 at 06:32
  • Okay another question, what do you use `jpg = bytes[a:b+2] bytes= bytes[b+2:]` for? I mean i get that it is extracting the jpg part from the stream again and again but why `b+2`? – praxmon Feb 25 '14 at 06:38
  • just `bytes[a:b]` without `b+2` will not contain `'\xff\xd9'`. so you need to add offset of 2 to accommodate for 2 bytes of end markers. – Zaw Lin Feb 25 '14 at 08:44
  • And lastly, can I display the jpg image using Tkinter itself? Not opencv? – praxmon Feb 25 '14 at 12:02
  • 2
    hmm...i got curious about how to do this as well. i have edited the answer. i know you got it solved in another question, but i think this method is better because the gui is going to hang in io errors etc in that answer. – Zaw Lin Feb 27 '14 at 08:00
  • 3
    Geez! you are the computer hacker! – Yuda Prawira Mar 30 '14 at 13:18
  • 1
    Any chance you could port this to Python 3? Strings and bytes and all that make this broken and I was unable to adapt it myself. – bugmenot123 Apr 16 '16 at 08:50
  • Ported it myself, posted as another answer. Thanks! – bugmenot123 Apr 17 '16 at 10:06
  • Hi Zan Lin. Thank for the answer. In my case it has the following error: IOError: ('http error', 401, 'Unauthorized', ). How would I modify to allow for authorization? :) – TNg Nov 03 '16 at 12:55
  • What type of authentication scheme? If it's basic, following something along the line of (http://stackoverflow.com/questions/635113/python-urllib2-basic-http-authentication-and-tr-im) should work. For other types, I have seen people recommending request library (http://docs.python-requests.org/en/latest/user/authentication/#digest-authentication). I have not tried any one of them though. But the only thing you need to change is the ```stream=urllib..```. maybe it will become 4 lines instead of one line. – Zaw Lin Nov 03 '16 at 15:00
  • I get an error ... TypeError: 'bytes' object is not callable – Sade Oct 13 '18 at 10:47
  • This is wrong solution, it generates broken frames. Because these magic numbers may present in the middle of JPEG data, so this solution will work by chance, most of frames will be ok, but some will be missing. This is correct way of doing this, in the bottom of the link: https://github.com/nodejs/node/issues/7533 (you should use HTTP delimiter, which comes with the header) – Stepan Yakovenko Feb 25 '20 at 01:10
  • As far as I know, the end markers cannot appear inside JPEG data unless there are jpegs embedded inside jpegs, in which case, the issue will happen for all frames and fairly easy to know that it is failing. – Zaw Lin Feb 28 '20 at 03:16
  • @bugmenot123 You can try to modify below code, it is ok for me in python3 Before bytes = bytes(1024) After bytes = b"" – kyc1109 Aug 05 '20 at 00:39
48

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)
bugmenot123
  • 1,069
  • 1
  • 18
  • 33
  • 1
    If you're trying to adapt the accepted answer to use python3's urllib, beware of changing the bytes.find('...') to bytes.find(b'...') - basically remember to specify that we're looking for byte! Otherwise won't work – mmagician Oct 19 '17 at 09:52
  • I get following error bytes = bytes() TypeError: 'str' object is not callable – Sade Oct 13 '18 at 11:09
  • @Sade I cannot reproduce that statement raising a TypeError in neither 2.7.15 nor 3.7.0. – bugmenot123 Oct 15 '18 at 08:59
  • Thanks for trying I used cap = cv2.VideoCapture('http:/5102.54/video/mjpeg/stream2') and it works. – Sade Oct 15 '18 at 11:46
  • It comes out that both work. Obviously the `VideoCapture` is shorter. – hao Apr 16 '19 at 03:55
  • 1
    Shorter but also way more robust as you rely on the well tested code of others instead of trying to parse bits and bytes yourself. ;) – bugmenot123 Apr 16 '19 at 08:46
  • Make sure you have the video url and not the snapshot url (like me) :) – user230910 Sep 11 '21 at 07:54
16

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))
Varun Chatterji
  • 5,059
  • 1
  • 23
  • 24
  • Varun, thanks for the answer. I am wondering if you'd know a way to use the same generator, that requests provides but somehow "return" that decoded image? Or to put it in other words, extract it outside of this part of code? I asked another answer but I don't expect it to get enough attention (https://stackoverflow.com/questions/44157160/returning-frame-streamed-received-by-requests-from-iterator-for-loop). – Michał Gacka May 25 '17 at 06:35
  • 2
    Defining a variable with name `bytes` will shadow the builtin `bytes` which is a type in Python 3. Better use another name. – isarandi May 13 '18 at 17:42
  • It seems that with the recent version of DroidCam app, `urlopen` call fails to fetch the stream. This code snippet using `requests` module works like a charm! – vyi Dec 25 '21 at 15:03
1

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
codeskyblue
  • 408
  • 4
  • 6
-2

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.