如何有效地阅读和保存IP摄像机的video?

我有一个python脚本,我通过我的家庭networking从ip摄像头抓取图像,并添加date时间信息。 在12小时的时间内,它可以抓取大约20万张照片。 但是在使用zoneminder (相机监控软件)时,相机在7小时内pipe理25万个。

我想知道是否有人可以帮助我提高我的脚本效率我已经尝试使用线程模块来创build2个线程,但它并没有帮助我不知道如果我已经实现了它的错误或不。 以下是我目前使用的代码:

#!/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() 

我怎么能改善这个脚本,或者我如何打开从相机的stream,然后从stream中抓取图像? 那甚至会提高效率/捕获率?

编辑:

感谢kobejohn的帮助,我想出了以下的实现。 运行了12个小时的时间,它已经从同一时间自己的线程上获得了来自2个独立摄像头(相同的时间)的420,000张图片,而从我上面的原始实现大约有20万张图片。 下面的代码将并行运行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() 

编辑(26/05/2014):

我已经花了2个月的更好的一部分,试图更新这个脚本/程序来使用Python 3,但已经完全无法让它做任何事情。 任何人都可以把我指向正确的方向吗?

我已经尝试了2to3脚本,但它只是改变了一些条目,我仍然无法得到它的function。

*编辑我以前指责GIL的行为是愚蠢的。 这是一个I / O绑定进程,而不是一个CPU绑定进程。 所以多处理不是一个有意义的解决方案。


*更新我终于找到了一个与您的流媒体接口(我认为)相同的演示IP摄像机。 使用流式接口,它只进行一次连接,然后从数据流中读取数据,就好像它是一个提取jpg图像帧的文件。 下面的代码,我抓住了2秒==> 27帧,我相信在7小时内推断约30万图像。

如果你想得到更多,你将图像修改和文件写入到一个单独的线程,并有一个工作人员做到这一点,而主线程只是从流中抓取,并将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捕捉似乎不够快,这是合乎逻辑的。 做这么多的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() 

你所得到的实施速度并不差。

你正在写约4.5帧每秒(fps),而zoneminder写出近10帧/秒。 下面是你的流程图和一些评论来加快速度

  1. 您正在读取网址缓冲区(网络延迟),
  2. 然后写图像(磁盘延迟1)(你可能不需要写图像到磁盘 – 考虑直接传递给你的img类)
  3. 读取图像(磁盘延迟2)
  4. 然后使用字体,文本框等操作图像…(3图像绘制) – 你可以用换行符创建一个字符串,以便您只能调用draw.text函数吗?
  5. 写输出图像(磁盘等待时间3)

有几件事可能会有所帮助。

  • 将字体打开从函数提升到主代码,然后传入字体对象(打开字体很可能是不平凡的时间,通过这样做一次,你没有花时间击中每个图像;你永远不会动态修改字体,所以共享相同的字体对象应该是OK)。

  • 您可能会报废包含三行中的两行:

    draw = ImageDraw.Draw(img)