简体   繁体   English

如何有效地从IP摄像机读取和保存视频?

[英]How to efficiently read and save video from an IP camera?

I have a python script I use to grab images from an ip camera through my home network and add date time information. 我有一个python脚本,可用来通过我的家庭网络从IP摄像机获取图像并添加日期时间信息。 In a 12 hour period it grabs about 200,000 pictures. 在12小时内,它可以捕获约200,000张照片。 But when using zoneminder (camera monitoring software) the camera manages 250,000 in a 7 hour period. 但是,当使用zoneminder (摄像机监控软件)时,摄像机可以在7小时内管理250,000。

I was wondering if anyone could help me improve my script efficiency I have tried using the threading module to create 2 threads but it has not helped i am not sure if I have implemented it wrong or not. 我想知道是否有人可以帮助我提高脚本效率,我曾尝试使用线程模块创建2个线程,但并不能帮助我不确定是否实现错误。 Below is code I am currently using: 以下是我当前正在使用的代码:

#!/usr/bin/env python

# My First python script to grab images from an ip camera

import requests
import time
import urllib2
import sys
import os
import PIL
from PIL import ImageFont
from PIL import Image
from PIL import ImageDraw
import datetime
from datetime import datetime
import threading

timecount = 43200
lock = threading.Lock()

wdir = "/workdir/"

y = len([f for f in os.listdir(wdir) 
     if f.startswith('Cam1') and os.path.isfile(os.path.join(wdir, f))])

def looper(timeCount):
   global y
   start = time.time()
   keepLooping = True
   while keepLooping:
    with lock:
        y += 1
    now = datetime.now()
    dte = str(now.day) + ":" +  str(now.month) + ":" + str(now.year)
    dte1 = str(now.hour) + ":" + str(now.minute) + ":" + str(now.second) + "." + str(now.microsecond)
    cname = "Cam1:"
    dnow = """Date: %s """ % (dte)
    dnow1 = """Time: %s""" % (dte1)
    buffer = urllib2.urlopen('http://(ip address)/snapshot.cgi?user=uname&pwd=password').read()
    img = str(wdir) + "Cam1-" + str('%010d' % y) + ".jpg"
    f = open(img, 'wb')
    f.write(buffer) 
    f.close()
    if time.time()-start > timeCount:
           keepLooping = False
    font = ImageFont.truetype("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf",10)
    img=Image.open(img)
    draw = ImageDraw.Draw(img)
    draw.text((0, 0),cname,fill="white",font=font)
    draw.text((0, 10),dnow,fill="white",font=font)
    draw.text((0, 20),dnow1,fill="white",font=font)
    draw = ImageDraw.Draw(img)
    draw = ImageDraw.Draw(img)
    img.save(str(wdir) + "Cam1-" + str('%010d' % y) + ".jpg")

for i in range(2):
        thread = threading.Thread(target=looper,args=(timecount,))
        thread.start()
        thread.join()

how could i improve this script or how do i open a stream from the camera then grab images from the stream? 如何改善此脚本或如何从相机打开流,然后从流中获取图像? would that even increase the efficiency / capture rate? 那会提高效率/捕获率吗?

Edit: 编辑:

Thanks to kobejohn's help i have come up with the following implementation. 感谢科比约翰的帮助,我提出了以下实施方案。 running for a 12 hour period it has gotten over 420,000 pictures from 2 seperate cameras (at the same tme) each running on their own thread at the same time compared to about 200,000 from my origional implementation above. 连续运行12个小时,它从2个单独的相机(在相同的tme上)同时在各自的线程上运行,已经获得了超过420,000张图片,而我上面的原始实现中大约有200,000张图片。 The following code will run 2 camera's in parallel (or close enough to it) and add text to them: 以下代码将并行运行2个摄像头(或距离足够近),并向其中添加文本:

import base64
from datetime import datetime
import httplib
import io
import os
import time

from PIL import ImageFont
from PIL import Image
from PIL import ImageDraw

import multiprocessing

wdir = "/workdir/"
stream_urlA = '192.168.3.21'
stream_urlB = '192.168.3.23'
usernameA = ''
usernameB = ''
password = ''

y = sum(1 for f in os.listdir(wdir) if f.startswith('CamA') and os.path.isfile(os.path.join(wdir, f)))
x = sum(1 for f in os.listdir(wdir) if f.startswith('CamB') and os.path.isfile(os.path.join(wdir, f)))

def main():
    time_count = 43200
#    time_count = 1
    procs = list()
    for i in range(1):
        p = multiprocessing.Process(target=CameraA, args=(time_count, y,))
        q = multiprocessing.Process(target=CameraB, args=(time_count, x,))
        procs.append(p)
        procs.append(q)
        p.start()
        q.start()
    for p in procs:
        p.join()

def CameraA(time_count, y):
    y = y
    h = httplib.HTTP(stream_urlA)
    h.putrequest('GET', '/videostream.cgi')
    h.putheader('Authorization', 'Basic %s' % base64.encodestring('%s:%s' % (usernameA, password))[:-1])
    h.endheaders()
    errcode, errmsg, headers = h.getreply()
    stream_file = h.getfile()
    start = time.time()
    end = start + time_count
    while time.time() <= end:
    y += 1
        now = datetime.now()
        dte = str(now.day) + "-" + str(now.month) + "-" + str(now.year)
        dte1 = str(now.hour) + ":" + str(now.minute) + ":" + str(now.second) + "." + str(now.microsecond)
        cname = "Cam#: CamA"
        dnow = """Date: %s """ % dte
        dnow1 = """Time: %s""" % dte1
        # your camera may have a different streaming format
        # but I think you can figure it out from the debug style below
        source_name = stream_file.readline()    # '--ipcamera'
        content_type = stream_file.readline()    # 'Content-Type: image/jpeg'
        content_length = stream_file.readline()   # 'Content-Length: 19565'
        #print 'confirm/adjust content (source?): ' + source_name
        #print 'confirm/adjust content (type?): ' + content_type
        #print 'confirm/adjust content (length?): ' + content_length
        # find the beginning of the jpeg data BEFORE pulling the jpeg framesize
        # there must be a more efficient way, but hopefully this is not too bad
        b1 = b2 = b''
        while True:
            b1 = stream_file.read(1)
            while b1 != chr(0xff):
                b1 = stream_file.read(1)
            b2 = stream_file.read(1)
            if b2 == chr(0xd8):
                break
        # pull the jpeg data
        framesize = int(content_length[16:])
        jpeg_stripped = b''.join((b1, b2, stream_file.read(framesize - 2)))
        # throw away the remaining stream data. Sorry I have no idea what it is
        junk_for_now = stream_file.readline()
        # convert directly to an Image instead of saving / reopening
        # thanks to SO: http://stackoverflow.com/a/12020860/377366
        image_as_file = io.BytesIO(jpeg_stripped)
        image_as_pil = Image.open(image_as_file)
        draw = ImageDraw.Draw(image_as_pil)
        draw.text((0, 0), cname, fill="white")
        draw.text((0, 10), dnow, fill="white")
        draw.text((0, 20), dnow1, fill="white")
        img_name = "CamA-" + str('%010d' % y) + ".jpg"
        img_path = os.path.join(wdir, img_name)
        image_as_pil.save(img_path)

def CameraB(time_count, x):
    x = x
    h = httplib.HTTP(stream_urlB)
    h.putrequest('GET', '/videostream.cgi')
    h.putheader('Authorization', 'Basic %s' % base64.encodestring('%s:%s' % (usernameB, password))[:-1])
    h.endheaders()
    errcode, errmsg, headers = h.getreply()
    stream_file = h.getfile()
    start = time.time()
    end = start + time_count
    while time.time() <= end:
    x += 1
        now = datetime.now()
        dte = str(now.day) + "-" + str(now.month) + "-" + str(now.year)
        dte1 = str(now.hour) + ":" + str(now.minute) + ":" + str(now.second) + "." + str(now.microsecond)
        cname = "Cam#: CamB"
        dnow = """Date: %s """ % dte
        dnow1 = """Time: %s""" % dte1
        # your camera may have a different streaming format
        # but I think you can figure it out from the debug style below
        source_name = stream_file.readline()    # '--ipcamera'
        content_type = stream_file.readline()    # 'Content-Type: image/jpeg'
        content_length = stream_file.readline()   # 'Content-Length: 19565'
        #print 'confirm/adjust content (source?): ' + source_name
        #print 'confirm/adjust content (type?): ' + content_type
        #print 'confirm/adjust content (length?): ' + content_length
        # find the beginning of the jpeg data BEFORE pulling the jpeg framesize
        # there must be a more efficient way, but hopefully this is not too bad
        b1 = b2 = b''
        while True:
            b1 = stream_file.read(1)
            while b1 != chr(0xff):
                b1 = stream_file.read(1)
            b2 = stream_file.read(1)
            if b2 == chr(0xd8):
                break
        # pull the jpeg data
        framesize = int(content_length[16:])
        jpeg_stripped = b''.join((b1, b2, stream_file.read(framesize - 2)))
        # throw away the remaining stream data. Sorry I have no idea what it is
        junk_for_now = stream_file.readline()
        # convert directly to an Image instead of saving / reopening
        # thanks to SO: http://stackoverflow.com/a/12020860/377366
        image_as_file = io.BytesIO(jpeg_stripped)
        image_as_pil = Image.open(image_as_file)
        draw = ImageDraw.Draw(image_as_pil)
        draw.text((0, 0), cname, fill="white")
        draw.text((0, 10), dnow, fill="white")
        draw.text((0, 20), dnow1, fill="white")
        img_name = "CamB-" + str('%010d' % x) + ".jpg"
        img_path = os.path.join(wdir, img_name)
        image_as_pil.save(img_path)

if __name__ == '__main__':
    main()

EDIT (26/05/2014): 编辑(2014年5月26日):

I have spent the better part of 2 months trying to update this script / program to work with python 3 but have been completely unable to get it to do anything. 我花了2个月的大部分时间来尝试更新此脚本/程序以使其与python 3兼容,但完全无法使它执行任何操作。 would anyone be able to point me in the right direction? 谁能指出我正确的方向?

I have tried the 2to3 script but it just changed a couple of entries and I still was unable to get it to function at all. 我尝试了2to3脚本,但是它只更改了几个条目,但我仍然无法使其完全起作用。

*edit I previously blamed GIL for the behavior which was silly. *编辑我以前指责GIL愚蠢的行为。 This is an I/O bound process, not a CPU-bound process. 这是一个I / O绑定的进程,而不是CPU绑定的进程。 So multiprocessing is not a meaningful solution. 因此,多处理不是有意义的解决方案。


*update I finally found a demo ip camera with the same streaming interface as yours (I think). *更新我终于找到了一个演示ip摄像机,该摄像机具有与您相同的流接口(我认为)。 Using the streaming interface, it only makes a connection once and then reads from the stream of data as if it were a file to extract jpg image frames. 使用流接口,它仅建立一次连接,然后从数据流中读取数据,就好像它是提取jpg图像帧的文件一样。 With the code below, I grabbed for 2 seconds ==> 27 frames which I believe extrapolates to about 300k images in a 7 hour period. 使用下面的代码,我抓了2秒钟==> 27帧,我相信可以在7个小时内推断出约30万张图像。

If you want to get even more, you would move the image modification and file writing to a separate thread and have a worker doing that while the main thread just grabs from the stream and sends jpeg data to the worker. 如果您想获得更多,可以将图像修改和文件写入移动到一个单独的线程中,并让工作人员执行该操作,而主线程只是从流中抓取并将jpeg数据发送给工作人员。

import base64
from datetime import datetime
import httplib
import io
import os
import time

from PIL import ImageFont
from PIL import Image
from PIL import ImageDraw


wdir = "workdir"
stream_url = ''
username = ''
password = ''


def main():
    time_count = 2
    looper_stream(time_count)


def looper_stream(time_count):
    h = httplib.HTTP(stream_url)
    h.putrequest('GET', '/videostream.cgi')
    h.putheader('Authorization', 'Basic %s' % base64.encodestring('%s:%s' % (username, password))[:-1])
    h.endheaders()
    errcode, errmsg, headers = h.getreply()
    stream_file = h.getfile()
    start = time.time()
    end = start + time_count
    while time.time() <= end:
        now = datetime.now()
        dte = str(now.day) + "-" + str(now.month) + "-" + str(now.year)
        dte1 = str(now.hour) + "-" + str(now.minute) + "-" + str(now.second) + "." + str(now.microsecond)
        cname = "Cam1-"
        dnow = """Date: %s """ % dte
        dnow1 = """Time: %s""" % dte1
        # your camera may have a different streaming format
        # but I think you can figure it out from the debug style below
        source_name = stream_file.readline()    # '--ipcamera'
        content_type = stream_file.readline()    # 'Content-Type: image/jpeg'
        content_length = stream_file.readline()   # 'Content-Length: 19565'
        print 'confirm/adjust content (source?): ' + source_name
        print 'confirm/adjust content (type?): ' + content_type
        print 'confirm/adjust content (length?): ' + content_length
        # find the beginning of the jpeg data BEFORE pulling the jpeg framesize
        # there must be a more efficient way, but hopefully this is not too bad
        b1 = b2 = b''
        while True:
            b1 = stream_file.read(1)
            while b1 != chr(0xff):
                b1 = stream_file.read(1)
            b2 = stream_file.read(1)
            if b2 == chr(0xd8):
                break
        # pull the jpeg data
        framesize = int(content_length[16:])
        jpeg_stripped = b''.join((b1, b2, stream_file.read(framesize - 2)))
        # throw away the remaining stream data. Sorry I have no idea what it is
        junk_for_now = stream_file.readline()
        # convert directly to an Image instead of saving / reopening
        # thanks to SO: http://stackoverflow.com/a/12020860/377366
        image_as_file = io.BytesIO(jpeg_stripped)
        image_as_pil = Image.open(image_as_file)
        draw = ImageDraw.Draw(image_as_pil)
        draw.text((0, 0), cname, fill="white")
        draw.text((0, 10), dnow, fill="white")
        draw.text((0, 20), dnow1, fill="white")
        img_name = "Cam1-" + dte + dte1 + ".jpg"
        img_path = os.path.join(wdir, img_name)
        image_as_pil.save(img_path)


if __name__ == '__main__':
    main()

*jpg capture below doesn't seem fast enough which is logical. 下面的* jpg捕获似乎不够快,这是合乎逻辑的。 making so many http requests would be slow for anything. 发出如此多的http请求对于任何事情来说都会很慢。

from datetime import datetime
import io
import threading
import os
import time

import urllib2

from PIL import ImageFont
from PIL import Image
from PIL import ImageDraw


wdir = "workdir"


def looper(time_count, loop_name):
    start = time.time()
    end = start + time_count
    font = ImageFont.truetype("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf", 10)
    while time.time() <= end:
        now = datetime.now()
        dte = str(now.day) + "-" + str(now.month) + "-" + str(now.year)
        dte1 = str(now.hour) + "-" + str(now.minute) + "-" + str(now.second) + "." + str(now.microsecond)
        cname = "Cam1-"
        dnow = """Date: %s """ % dte
        dnow1 = """Time: %s""" % dte1
        image = urllib2.urlopen('http://(ip address)/snapshot.cgi?user=uname&pwd=password').read()
        # convert directly to an Image instead of saving / reopening
        # thanks to SO: http://stackoverflow.com/a/12020860/377366
        image_as_file = io.BytesIO(image)
        image_as_pil = Image.open(image_as_file)
        draw = ImageDraw.Draw(image_as_pil)
        draw_text = "\n".join((cname, dnow, dnow1))
        draw.text((0, 0), draw_text, fill="white", font=font)
        #draw.text((0, 0), cname, fill="white", font=font)
        #draw.text((0, 10), dnow, fill="white", font=font)
        #draw.text((0, 20), dnow1, fill="white", font=font)
        img_name = "Cam1-" + dte + dte1 + "(" + loop_name + ").jpg"
        img_path = os.path.join(wdir, img_name)
        image_as_pil.save(img_path)


if __name__ == '__main__':
    time_count = 5
    threads = list()
    for i in range(2):
        name = str(i)
        t = threading.Thread(target=looper, args=(time_count, name))
        threads.append(p)
        t.start()
    for t in threads:
        t.join()

The speeds you are getting for the implementation you have given are not bad. 您提供的实现所获得的速度还不错。

You are writing about 4.5 frames per second (fps), and zoneminder is writing out nearly 10 fps. 您每秒写大约4.5帧(fps),zoneminder每秒写大约10 fps。 Below is your flow diagram with a few comments to speed things up 以下是您的流程图,其中包含一些注释以加快处理速度

  1. You are reading the url buffer (network latency), 您正在读取网址缓冲区(网络延迟),
  2. then writing the image (disk latency 1) (you might not need to write the image here to disk - consider passing it directly to your img class) 然后写入映像(磁盘延迟1)(您可能不需要在此处将映像写入磁盘-考虑将其直接传递给img类)
  3. reading the image (disk latency 2) 读取图像(磁盘延迟2)
  4. then manipulating the image using fonts, text boxes etc... (3 image draws) - Can you build one string with newlines so that you only make one call to the draw.text function? 然后使用字体,文本框等操作图像...(3张图像绘制)-是否可以用换行符构建一个字符串,以便仅对draw.text函数进行一次调用?
  5. writing the output image (disk latency 3) 写入输出映像(磁盘延迟3)

There are a couple of things that might help. 有几件事可能会有所帮助。

  • Lift the font-opening from the function to the main code, then pass in the font object (opening a font is likely to be non-trivial in time, by doing it once, you're not taking the time hit for every image; you never modify the font on the fly, so sharing the same font object should be OK). 将打开字体的功能从函数移至主代码,然后传入字体对象(打开字体在时间上可能很重要,只需执行一次,就不会浪费每个图像的时间;您永远不会即时修改字体,因此共享相同的字体对象应该可以)。

  • You can probably scrap two of the three lines that contain: 您可能会报废包含以下内容的三行中的两行:

    draw = ImageDraw.Draw(img) 画= ImageDraw.Draw(img)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM