2013-10-11 2 views
2

У меня есть сценарий python, который я использую для захвата изображений с ip-камеры через домашнюю сеть и добавления информации о времени. Через 12 часов он захватывает около 200 000 снимков. Но при использовании zoneminder (программное обеспечение для мониторинга камеры) камера управляет 250 000 в течение 7 часов.Как эффективно читать и сохранять видео с 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() 

, как я мог улучшить этот скрипт или как я открыть поток из камеры затем захватить изображения из потока? может ли это повысить коэффициент эффективности/захвата?

Edit:

Благодаря помощи kobejohn, я придумал следующую реализацию. работая в течение 12-ти часового периода, он получил более 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 и суммы (вместо len, которые нуждаются в последовательности) как: 'sum (1 для f в os.listdir (wdir), если f.startswith ('CamFront') и os.path.isfile (os.path.join (wdir, f)))' –

+0

хорошо, что часть - это просто проверить, есть ли в рабочем каталоге изображения, чтобы узнать, начинается ли счетчик с 1 или другого номера. это больше скорость захвата, которую я пытаюсь улучшить в функции петлителя. и для этого улучшения вы говорите, что замените всю y = часть только y = sum (1 для f в os.listdir (wdir), если f.startswith ('CamFront')? – ButtzyB

+0

Я тоже новый ученик Python. где 'sun (выражение genrator)' лучше, чем 'len ([listcompresion])'. Конечно, это не ответ на ваш вопрос. Хотел бы я, но на данном этапе я не могу вносить свой вклад :(:( –

ответ

3

* edit Я ранее обвинял GIL в поведении, которое было глупо. Это процесс привязки ввода-вывода, а не процесс, связанный с ЦП. Поэтому многопроцессорность не является значимым решением.


* update Я наконец нашел демонстрационную ip-камеру с тем же потоковым интерфейсом, что и ваш (я думаю). Используя потоковый интерфейс, он только делает соединение один раз, а затем считывает из потока данных, как если бы это был файл для извлечения кадров jpg-изображений. С приведенным ниже кодом я схватил за 2 секунды ==> 27 кадров, которые, я считаю, экстраполируют примерно до 300 тыс. Изображений в 7-часовой период.

Если вы хотите получить еще больше, вы переместите изменение изображения и запись файла в отдельный поток и попросите работника сделать это, пока основной поток просто захватывает поток и отправляет данные 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 секунд все изображения второго процесса были сделаны через 10 секунд после процесса 1. Таким образом, процесс 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

Я думаю, что i origionaly попытался перенести прямо из буфера, добавив текст (в команде 1) и только один раз напишет файл. Но запись текста не сработает, потому что файл должен быть написан, иначе при попытке добавить текст произошла ошибка в коррупции. есть ли способ открыть видеопоток в python, чтобы сказать/dev/null и захватить изображения из потока? @Paul – ButtzyB

+0

ButtzyB: Я не знаю таких вещей в библиотеках по умолчанию. Вы рассматривали построение одной строки со всей информацией о времени и одним вызовом draw.text? – Paul

+0

Я пробовал это, но каждый путь, который я пробовал, заканчивается одной строкой текста, разделенной белой коробкой. Я думаю, что кобеджон столкнулся с той же проблемой, пытаясь получить одну строку, помогая мне собрать новую реализацию. @Павел – ButtzyB

0

Есть несколько вещей, которые могли бы помочь.

  • Лифт шрифт открытие из функции в основной код, а затем передать в объект шрифта (открытие шрифта, вероятно, будет нетривиальным во времени, делая это один раз, вы не принимая время для каждого изображения, вы никогда не изменяете шрифт «на лету», поэтому совместное использование одного и того же объекта шрифта должно быть в порядке).

  • Вы, вероятно, лом два из трех строк, содержащие:

    ничьих = ImageDraw.Draw (IMG)

Смежные вопросы