1. 程式人生 > >樹莓派+python opencv實現遠端監控

樹莓派+python opencv實現遠端監控

近來風聞住宿地不太安全,正好手邊有個樹莓派,花了些時間用樹莓派實現了遠端監控,下面和大家分享一下,希望有所幫助。

因為非計算機視覺專業人士,所以使用了python版的opencv,方便快捷。如何在pc上安裝python opencv見http://luugiathuy.com/2011/02/setup-opencv-for-python/,曾經見著有中文的橋段找不著在哪裡了,對不住了各位不喜英文的童鞋。最後再裝上python imaging library。

安裝完成後,在opencv/sample/python目錄下有一個camera.py檔案,先看此原始碼:

import cv2.cv as cv
import time

cv.NamedWindow("camera", 1)

capture = cv.CaptureFromCAM(0)

while True:
    img = cv.QueryFrame(capture)
    cv.ShowImage("camera", img)
    if cv.WaitKey(10) == 27:
        break
cv.DestroyAllWindows()
基本上意思就是說,建立一個源於預設攝像裝置的捕捉器,然後不停地一幀一幀地獲取影象並顯示。按照上述程式碼,要實現遠端監控,一種很簡單直接的方法就是在資料來源端獲取影象之後通過網路傳出去,然後在遠端端讀取出影象並顯示。這樣可以將整個程式分為三部分:資料來源(也就是要監控的地方),伺服器(用於中轉網路資料),客戶端(顯示監控影象的地方)。下面逐項講解。

一、

資料來源端也就是放攝像頭的地方。其實,完全可以把電腦開著放在那裡然後開著QQ,遠端視訊聊天即可實現監控。不過這樣做略顯低端,說出去都有失碼農身份。正好手邊有一樹莓派,正是派上用場的地方。恩,嵌入式開發,聽著高階多了。閒話少說,先講樹莓派的配置。首先,用的是Raspbian系統,最好能夠先執行sudo apt-get update和sudo apt-get upgrade,保證系統是最新的。系統自帶python,不需要再安裝;安裝python opencv:sudo apt-get install libopencv-dev python-opencv;安裝python imaging library:sudo apt-get install python-imaging。大功告成。

傳輸網路資料就用最基本的socket,那麼,樹莓派上的程式碼就如下所示:

import cv
import time, socket, Image, StringIO

capture = cv.CaptureFromCAM(0)
cv.SetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH, 640)
cv.SetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT, 480)

HOST, PORT = "192.168.0.102", 9999
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))

while True:
    img = cv.QueryFrame(capture)
    pi = Image.fromstring("RGB", cv.GetSize(img), img.tostring())
    buf = StringIO.StringIO()
    pi.save(buf, format = "JPEG")
    jpeg = buf.getvalue()
    buf.close()
    transfer = jpeg.replace("\n", "\-n")
    print len(transfer), transfer[-1]
    sock.sendall(transfer + "\n")
    time.sleep(0.5)
    
sock.close()
上述程式碼有幾處需要解釋的地方:

pi = Image.fromstring(...)

此處用到了python imaging library(PIL),用獲取到的幀建立了一個Image例項。這樣做的目的在於減少傳輸的資料量。一幅640x480的RGB影象,原始資料長度為640x480x3=921600Byte,對於2M頻寬的小水管來說太多了。這裡用PIL來壓縮成jpeg格式,能大大減少資料量。opencv本身也提供了儲存為jpeg的函式,但可能是由於壓縮程度不同。opencv得到的jpeg影象大小為60kb,而PIL得到的jpeg影象僅為19kb。

buf = StringIO.StringIO()

pi.save(buf, ...)

Image提供的save方法,需要將jpeg格式的影象存入一個檔案中。但這裡顯然不需要也最好不要將影象寫進硬碟,因此需要一個記憶體中的“檔案”,這就是StringIO。

transfer = jpeg.replace(...)

socket只是一個持續的流,讀取時需要能斷句,因此將資料中的“\n”替換掉,然後在一幀資料之後手工加上一個“\n”。

time.sleep(0.5)

用來控制fps,0.5相當於是2 fps。

樹莓派支援很多種的攝像頭,詳見http://elinux.org/RPi_VerifiedPeripherals,這裡用的是微軟的LifeCam VX 800,即插即用。執行上述程式,恩?視窗是黑的?select timeout?樓主坑爹啊!!!別急,這是因為攝像頭自帶的麥克風不相容。在/etc/modprobe.d/下新建一個檔案camera-blacklist.conf,寫上blacklist snd_usb_audio,拔出攝像頭,然後rmmod snd_usb_audio,再插上攝像頭,即可。若還未解決,屬頑固問題,請參考http://www.raspberrypi.org/phpBB3/viewtopic.php?t=35689&p=314596

二、

伺服器端用來中轉資料,主旨是提供一個外網ip。上程式碼:

import socket, time

sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.bind(("192.168.0.102", 9999))
sock.listen(2)

src, src_addr = sock.accept()
print "Source Connected by", src_addr

dst, dst_addr = sock.accept()
print "Destination Connected by", dst_addr

while True:
    msg = src.recv(1024 * 1024)
    #print len(msg)
    if not msg:
        break
    try:
        dst.sendall(msg)
    except Exception as ex:
        dst, dst_addr = sock.accept()
        print "Destination Connected Again By", dst_addr
    except KeyboardInterrupt:
        print "Interrupted"
        break

src.close()
dst.close()
sock.close()
時間有限,寫得非常簡單:等兩個連線,頭一個是資料來源,第二個是客戶端;收到從資料來源來的資料就轉發給客戶端。有志於做成多點連線的童鞋請自行修改邏輯。

三、

客戶端接受到資料之後還原為影象顯示出來即可,相當於是資料來源的逆操作。程式碼如下:

import cv2.cv as cv
import socket, time, Image, StringIO

HOST, PORT = "192.168.0.102", 9999
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
f = sock.makefile()

cv.NamedWindow("camera_server")

while True:
    msg = f.readline()
    if not msg:
        break
    print len(msg), msg[-2]
    jpeg = msg.replace("\-n", "\n")
    buf = StringIO.StringIO(jpeg[0:-1])
    buf.seek(0)
    pi = Image.open(buf)
    img = cv.CreateImageHeader((640, 480), cv.IPL_DEPTH_8U, 3)
    cv.SetData(img, pi.tostring())
    buf.close()
    cv.ShowImage("camera_server", img)
    if cv.WaitKey(10) == 27:
        break

sock.close()
cv.DestroyAllWindows()
先逆轉換“\n”,然後將接收到的資料用PIL開啟,從中建立img,並顯示在視窗中。注意,如果在資料來源處改動了640x480的解析度,這裡也要更改。

按照上述方法,就可以實現簡單的遠端監控。當然,上面的程式碼遠非完善,還有很多可以修改的地方。

無版權,歡迎轉載,共勉之。