2013-10-11 46 views
2

我有一個python腳本,用於通過家庭網絡從ip攝像頭抓取圖像並添加日期時間信息。在12小時的時間內,它可以拍攝大約200,000張照片。但是,當使用zoneminder(相機監控軟件)時,相機在7小時內管理250,000。如何從IP攝像機高效地讀取和保存視頻?

我在想,如果有人可以幫助我提高我的劇本效率我已經使用線程模塊來創建2個線程嘗試,但它並沒有幫助我不知道我是否已經實現了它是不是錯了。下面是代碼,我目前正在使用:

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

我怎麼能改善這個腳本或者我如何從相機中打開一個流,然後從流中搶圖像?那甚至會提高效率/捕獲率?

編輯:

由於kobejohn的幫助下,我已經提出了下面的實現。運行了12小時的時間,它已經從2臺獨立攝像機(同一臺計算機)上獲得了超過420,000張照片,每張照片在同一時間都在自己的線程上運行,相比之下,我的原始實現大約有200,000張。下面的代碼將運行在平行(或足以使其接近)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):

我已經花了更好的一部分2個月試圖更新這個腳本/程序來使用python 3,但已完全無法讓它做任何事情。任何人都可以指出我正確的方向嗎?

我已經試過了2to3的腳本,但它只是改變了一些條目,我仍然無法得到它的所有功能。

+0

一個變化,或者可以是小改善使用genratar表達和總和(代替如果f.startswith('CamFront')和os.path.isfile(os.path.join(wdir,f)))''sum(1 for f in os.listdir(wdir))' –

+0

那麼該部分只是爲了檢查工作目錄中是否存在圖像,以確定計數器是從1開始還是從另一個開始。它更多的是我試圖改善活套功能的捕獲速度。如果f.startswith('CamFront')? – ButtzyB

+0

我也是新的Python學習者,我只是讀了一些在哪裏'sun(genrator表達式)'比'len([listcompresion])更好'當然這不是你的問題的答案,我希望我可以但在這個階段我無法提供:(:( –

回答

3

*編輯我先前指責爲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() 
+0

感謝您的答覆我試圖使用多處理那裏,但與運行腳本10秒從第二個進程的所有圖像都是在進程1後10秒。所以進程1跑10秒拍攝照片,然後處理2跑10秒鐘並拍攝更多照片。 @ kobejohn – ButtzyB

+0

沒錯。傻我。如果你加入,那麼它會在第二個之前等待第一個完成。移出連接。我更新了代碼。 – KobeJohn

+0

我認爲他們可能正在工作,他們完成了大約0.2秒的時間,現在整個10秒鐘的試驗間隔最多分開一次,幾乎完全在60秒的試驗中同時進行。如果我將第二個攝像機環路添加到混音中,就像每個攝像頭擁有4個線程2一樣嗎?或者我是否需要像創建過程那樣用q來代替p來創建過程? @kobejohn – ButtzyB

1

您得到的執行速度並不差。

你正在編寫約每秒4.5幀(fps),並zoneminder是寫了近10幀。下面是你的流程圖與幾點意見加快速度

  1. 您正在閱讀的URL緩衝區(網絡延遲),
  2. 然後寫入圖像(磁盤延遲1)(您可能不需要寫圖像到磁盤 - 考慮直接傳遞給你的img類)
  3. 閱讀圖像(磁盤等待時間2)
  4. 然後使用字體,文本框等操縱圖像...(3圖像繪製) - 你可以建立一個帶有換行符的字符串,以便您只對draw.text函數進行一次調用?
  5. 寫輸出圖像(磁盤延遲3)
+0

我想我origionaly嘗試直接從緩衝區轉移到添加文本(在1命令),只寫一次文件。但是寫入文本將不起作用,因爲必須寫入文件,否則在嘗試添加文本時會出現有關文件損壞的錯誤。有沒有辦法打開Python中的視頻流說/ dev/null並從流中抓取圖像? @Paul – ButtzyB

+0

ButtzyB:我不知道默認庫中的這些東西。您是否考慮過使用所有時間戳信息和一個調用draw.text的函數來構建一個字符串? – Paul

+0

我試過了,但是我嘗試過的每種方法都以單行文本分隔的白色方框結束。我認爲kobejohn遇到了同樣的麻煩,試圖獲得一個單一的字符串,同時幫助我整理新的實現。 @保羅 – ButtzyB

0

有一對夫婦的事情,可能的幫助。

  • 電梯從功能到主代碼的字體打開,然後傳遞字體對象(打開字體很可能是在時間上不平凡的,通過做一次,你不走每個圖像的時間點擊;你永遠不會動態修改字體,所以共享相同的字體對象應該是OK)。

  • 你可能就廢2包含三行:平局

    = ImageDraw.Draw(IMG)

相關問題