簡體   English   中英

如何有效地從IP攝像機讀取和保存視頻?

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

我有一個python腳本,可用來通過我的家庭網絡從IP攝像機獲取圖像並添加日期時間信息。 在12小時內,它可以捕獲約200,000張照片。 但是,當使用zoneminder (攝像機監控軟件)時,攝像機可以在7小時內管理250,000。

我想知道是否有人可以幫助我提高腳本效率,我曾嘗試使用線程模塊創建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()

如何改善此腳本或如何從相機打開流,然后從流中獲取圖像? 那會提高效率/捕獲率嗎?

編輯:

感謝科比約翰的幫助,我提出了以下實施方案。 連續運行12個小時,它從2個單獨的相機(在相同的tme上)同時在各自的線程上運行,已經獲得了超過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()

編輯(2014年5月26日):

我花了2個月的大部分時間來嘗試更新此腳本/程序以使其與python 3兼容,但完全無法使它執行任何操作。 誰能指出我正確的方向?

我嘗試了2to3腳本,但是它只更改了幾個條目,但我仍然無法使其完全起作用。

*編輯我以前指責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 fps。 以下是您的流程圖,其中包含一些注釋以加快處理速度

  1. 您正在讀取網址緩沖區(網絡延遲),
  2. 然后寫入映像(磁盤延遲1)(您可能不需要在此處將映像寫入磁盤-考慮將其直接傳遞給img類)
  3. 讀取圖像(磁盤延遲2)
  4. 然后使用字體,文本框等操作圖像...(3張圖像繪制)-是否可以用換行符構建一個字符串,以便僅對draw.text函數進行一次調用?
  5. 寫入輸出映像(磁盤延遲3)

有幾件事可能會有所幫助。

  • 將打開字體的功能從函數移至主代碼,然后傳入字體對象(打開字體在時間上可能很重要,只需執行一次,就不會浪費每個圖像的時間;您永遠不會即時修改字體,因此共享相同的字體對象應該可以)。

  • 您可能會報廢包含以下內容的三行中的兩行:

    畫= ImageDraw.Draw(img)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM