以及流量問題 ,我只希望能夠用網頁觀看,在多個不同的裝置(PC ,平板,手機)。當然
也會有些許的延遲(幾秒鐘),接下來也會繼續嘗試用其他的方式,讓影像不要Delay那麼嚴
重,在此先做個筆記。
一、環境說明
(一)Raspberry Pi B+
os 為 wheezy ,使用python 2.7 搭配OpenCV 2.4.10 。
網卡 Edimax EW-7811Un
(二)WebCam 為Logitech C170
(三)WebSocket Server 由 GlassFish 4.1 + Java 實作。
(四)觀看端由 Html + JavaScript 實作。
二、整體架構圖
三、架構說明:
(一) 輸入端由OpenCV開啟Webcam取得影像 ,以Jpeg作為壓縮格式後,轉成
Base64字串,由WebSocket client端發送(為了節省頻寬,目前品質壓到30%)。
(二) websocket 由python 的websocket-client package ,安裝方式,請參考
[Python] 用Python 做 WebSocket Client
(三)WebSocket Server在onMessage (收到圖片時)會傳給所有連線中的Client,
除了發送圖片的Raspberry Pi。
(四) 觀看端在網頁載入完成後,會連線到WebSocket Server,等待接收訊息。當觀看端的onMessage事件發生時,會解碼收到的圖片訊息,並且顯示在網頁上,並更新時間。
四、程式碼
(一) Raspberry Pi 上的發送圖片程式 Webcam.py
import sys
import cv
import cv2
import numpy
import base64
from websocket import create_connection
#connection to websocket server
ws = create_connection('ws://192.168.0.101:8080/DemoEchoServer/echo')
#JPEG encode_param
encode_param=[int(cv2.IMWRITE_JPEG_QUALITY),30]
#select first VideoCapture
capture = cv2.VideoCapture(0)
#set WebCame Frame Width
capture.set(3,640)
#set WebCam Frame Height
capture.set(4,480)
#defind function
def repeat():
# Capture frame-by-frame
ret, frame = capture.read()
#encode to jpg
if (type(ret)!=type(None)):
result,imgencode=cv2.imencode('.jpg',frame,encode_param )
# to numpy array
data = numpy.array(imgencode)
# tostring
stringData = data.tostring()
#encode to base64
encoded = base64.b64encode(stringData)
#send
ws.send(encoded)
#sleep 60millisecond
cv2.waitKey(60)
while True:
repeat()
(二)WebSocket Server EchoServer.java
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/echo")
public class EchoServer {
//用來存放Session的集合
private static final Set sessions = Collections
.synchronizedSet(new HashSet());
@OnOpen
public void onOpen(Session session,EndpointConfig config) {
session.setMaxBinaryMessageBufferSize(1024*1024);
session.setMaxTextMessageBufferSize(1024*1024);
System.out.println(session.getId()+" open");
//把session加入集合中
sessions.add(session);
}
@OnClose
public void onClose(Session session) {
System.out.println(session.getId()+" close");
//把session從集合中移除
sessions.remove(session);
}
@OnMessage
public void onMessage(String message , Session session) throws IOException {
try
{
//sent Image
for(Session session2 : sessions)
{
if (!session2.getId().equals(session.getId()))
{
session2.getAsyncRemote().sendText(message);
}
//session2.getBasicRemote().sendText(message);
}
catch (Exception e)
{
e.printStackTrace();;
}
}
@OnError
public void onError(Throwable t) {
t.printStackTrace();
}
}
(三)觀看端 display.html
1: <html>
2: <head>
3: <title>TODO supply a title</title>
4: <meta charset="UTF-8">
5: <meta name="viewport" content="width=device-width, initial-scale=1.0">
6: <script language="javascript" type="text/javascript">
7: //echo server的網址
8: var wsUri = "ws://192.168.0.101:8080/DemoEchoServer/echo";
9: //定義元件
10: var outputElement;
11: //初始化function
12: function init()
13: {
14: outputElement = document.getElementById("output");
15: live();
16: }
17: //執行websocket
18: function live() {
19: //連線到echo server
20: websocket = new WebSocket(wsUri);
21: //連線成功要執行的function
22: websocket.onopen = function (evt) {
23: onOpen(evt)
24: };
25: //連結結速要執行的function
26: websocket.onclose = function (evt) {
27: onClose(evt)
28: };
29: //收到echo server傳來的訊息要做的function
30: websocket.onmessage = function (evt) {
31: onMessage(evt)
32: };
33: //當發生錯誤時要執行的function
34: websocket.onerror = function (evt) {
35: onError(evt)
36: };
37: }
38: //連線成功後,在output Element顯示時間
39: function onOpen(evt) {
40: var currentdate = new Date();
41: var datetime = "Last Sync: " + currentdate.getDate() + "/"
42: + (currentdate.getMonth()+1) + "/"
43: + currentdate.getFullYear() + " @ "
44: + currentdate.getHours() + ":"
45: + currentdate.getMinutes() + ":"
46: + currentdate.getSeconds();
47: writeToScreen(datetime+" onOpen")
48: }
49: //當中斷連線時輸出訊息在outputElement上
50: function onClose(evt) {
51: var currentdate = new Date();
52: var datetime = "Last Sync: " + currentdate.getDate() + "/"
53: + (currentdate.getMonth()+1) + "/"
54: + currentdate.getFullYear() + " @ "
55: + currentdate.getHours() + ":"
56: + currentdate.getMinutes() + ":"
57: + currentdate.getSeconds();
58: writeToScreen(datetime+" onClose")
59: }
60: //收到訊息時顯示在螢幕上, 並且解密base64圖片資料
61: function onMessage(evt) {
62: var currentdate = new Date();
63: var datetime = "Last Sync: " + currentdate.getDate() + "/"
64: + (currentdate.getMonth()+1) + "/"
65: + currentdate.getFullYear() + " @ "
66: + currentdate.getHours() + ":"
67: + currentdate.getMinutes() + ":"
68: + currentdate.getSeconds()+" "
69: + currentdate.getTime();
70: writeToScreen(datetime+" onMessage")
71: document.getElementById("ItemPreview").src = "data:image/png;base64," + evt.data;
72: }
73: //發生錯誤時顯示訊息在outputElement上
74: function onError(evt) {
75: var currentdate = new Date();
76: var datetime = "Last Sync: " + currentdate.getDate() + "/"
77: + (currentdate.getMonth()+1) + "/"
78: + currentdate.getFullYear() + " @ "
79: + currentdate.getHours() + ":"
80: + currentdate.getMinutes() + ":"
81: + currentdate.getSeconds();
82: writeToScreen(datetime+" onError")
83: }
84: //發送訊息
85: function doSend(message) {
86: writeToScreen("己傳送 " + message);
87: websocket.send(message);
88: }
89: //輸出訊息在outputElement上
90: function writeToScreen(message) {
91: var pre = document.getElementById("myp");
92: pre.style.wordWrap = "break-word";
93: pre.innerHTML = message;
94: output.appendChild(pre);
95: }
96: //加入事件,當載入完成後執行init()
97: window.addEventListener("load", init, false);
98: </script>
99: </head>
100: <body>
101: <div id="output"> <p id="myp"></div>
102: <div>
103: <img id="ItemPreview" src="" />
104: </div>
105: </body>
106: </html>
五、執行程式
(一)先開啟WebSocket Server。
(二)在Raspberry Pi上輸入 python Webcam.py
(三)開啟display.html觀看。
六、執行結果
七、待解決問題
(一) 畫面延遲問題, 以目前看來至少會慢個約5秒,慢的主要原因是資料量,每一次
都發送一張完整的圖片 (640 *480) ,樹梅派只有單核心, 要一直取影像再壓成JPEG傳送,
可能會有點吃力。
(二) 透過websocket Server 再做一次分派,好處是可以讓多個裝置同時間做觀看。雖然每一個之間還是會有Delay。把傳送影像用的session2.getBasicRemote().sendText(message); 改成用session2.getAsyncRemote().sendText(message); 效能看起來些許的提昇。
.png)
沒有留言:
張貼留言