如何使用OpenCV為桌面和Web構(gòu)建簡(jiǎn)單的Webcam應(yīng)用程序(二)
Dynamic Web TWAIN是一個(gè)專為Web應(yīng)用程序設(shè)計(jì)的TWAIN掃描識(shí)別控件。你只需在TWAIN接口寫(xiě)幾行代碼,就可以用兼容TWAIN的掃描儀掃描文檔或從數(shù)碼相機(jī)/采集卡中獲取圖像。然后用戶可以編輯圖像并將圖像保存為多種格式,用戶可保存圖像到遠(yuǎn)程數(shù)據(jù)庫(kù)或者SharePoint。這個(gè)TWAIN控件還支持上傳和處理本地圖像。
點(diǎn)擊下載Dynamic Web TWAIN正式版
從任何Web瀏覽器訪問(wèn)遠(yuǎn)程網(wǎng)絡(luò)攝像頭
要實(shí)現(xiàn)遠(yuǎn)程網(wǎng)絡(luò)攝像頭訪問(wèn),我只需要?jiǎng)?chuàng)建一個(gè)帶有圖像元素的簡(jiǎn)單HTML頁(yè)面并啟動(dòng)一個(gè)簡(jiǎn)單的HTTP服務(wù)器,即可通過(guò)HTTP GET請(qǐng)求連續(xù)發(fā)送網(wǎng)絡(luò)攝像頭幀。為了使代碼盡可能簡(jiǎn)單,我只使用了四種編程語(yǔ)言的內(nèi)置HTTP API和OpenCV視頻捕獲API。
Node.js
創(chuàng)建一個(gè)簡(jiǎn)單的HTML頁(yè)面:
使用setTimeout()從Web服務(wù)器連續(xù)獲取新圖像并刷新圖像元素。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Webcam</title> </head> <body> <img id="image"/> <script type="text/javascript"> var image = document.getElementById('image'); function refresh() { image.src = "/image?" + new Date().getTime(); image.onload= function(){ setTimeout(refresh, 30); } } refresh(); </script> </body> </html>
在服務(wù)器端,使用HTTP模塊創(chuàng)建一個(gè)簡(jiǎn)單的Web服務(wù)器:
const http = require('http'); var server = http.createServer(function (req, res) { if (req.url === '/' || req.url === '/index.htm') { res.writeHead(200, { 'Content-Type': 'text/html' }); res.write(html); res.end(); } else if (req.url.startsWith("/image")) { res.writeHead(200, { 'Content-Type': 'image/jpeg' }); res.write(img); res.end(); } else res.end('Invalid Request!'); }); server.listen(2020);分析請(qǐng)求URL,然后發(fā)送相應(yīng)的響應(yīng)。同時(shí),創(chuàng)建一個(gè)計(jì)時(shí)器以捕獲網(wǎng)絡(luò)攝像頭幀并將其編碼為JPEG格式:
var img = null; function capture() { var frame = wCap.read() if (frame.empty) { wCap.reset(); frame = wCap.read(); } img = cv.imencode('.jpg', frame); setTimeout(capture, 30); } capture();要通過(guò)雙擊打開(kāi)HTML文件,請(qǐng)將相對(duì)圖像路徑更改為絕對(duì)圖像路徑:
image.src = "http://localhost:2020/image?" + new Date().getTime();C#
受本杰明·薩默頓(Benjamin Summerton)的要旨啟發(fā),我創(chuàng)建了HTTP服務(wù)器,如下所示:
using System; using System.IO; using System.Text; using System.Net; using System.Threading.Tasks; using OpenCvSharp; namespace Web { class Program { public static HttpListener listener; public static string url = "http://localhost:2020/"; public static string pageData = "" + "" + "" + "蟒蛇HttpListener Example " + "" + "" + ""+ " "+ "" + ""; public static VideoCapture capture = new VideoCapture(0); public static async Task HandleIncomingConnections() { while (true) { HttpListenerContext ctx = await listener.GetContextAsync(); HttpListenerRequest req = ctx.Request; HttpListenerResponse resp = ctx.Response; if ((req.HttpMethod == "GET") && (req.Url.AbsolutePath.StartsWith("/image"))) { resp.ContentType = "image/jpeg"; using (Mat image = new Mat()) { capture.Read(image); Cv2.ImEncode(".jpg", image, out var imageData); await resp.OutputStream.WriteAsync(imageData, 0, imageData.Length); resp.Close(); } } else { // Write the response info byte[] data = Encoding.UTF8.GetBytes(pageData); resp.ContentType = "text/html"; resp.ContentEncoding = Encoding.UTF8; resp.ContentLength64 = data.LongLength; // Write out to the response stream (asynchronously), then close it await resp.OutputStream.WriteAsync(data, 0, data.Length); resp.Close(); } } } static void Main(string[] args) { // Create a Http server and start listening for incoming connections listener = new HttpListener(); listener.Prefixes.Add(url); listener.Start(); Console.WriteLine("Listening for connections on {0}", url); // Handle requests Task listenTask = HandleIncomingConnections(); listenTask.GetAwaiter().GetResult(); // Close the listener listener.Close(); } } }
使用Python的內(nèi)置HTTP服務(wù)器類非常方便。我們需要做的是定義一個(gè)用于處理HTTP請(qǐng)求的自定義處理程序:
import http.server import socketserver class MyHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): if self.path == '/': self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() self.wfile.write(bytes(pageData, "utf8")) elif self.path.startswith('/image'): self.send_response(200) self.send_header("Content-type", "image/jpeg") self.end_headers() ret, frame = cap.read() _, jpg = cv2.imencode(".jpg", frame) self.wfile.write(jpg) else: self.send_response(404) with socketserver.TCPServer(("", PORT), MyHandler) as httpd: print("Serving at port ", PORT) try: httpd.serve_forever() except: pass
高朗
像Python一樣,很容易在30秒內(nèi)設(shè)置HTTP Web服務(wù)器:func handler(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { pageData := "" + "" + "" + "HttpListener Example " + "" + "" + "" + " " + "" + "" w.Header().Set("Content-Type", "text/html") w.Write([]byte(pageData)) } else if strings.HasPrefix(r.URL.Path, "/image") { webcam.Read(&img) jpg, _ := gocv.IMEncode(".jpg", img) w.Write(jpg) } else { fmt.Fprintf(w, "Page Not Found") } } func main() { fmt.Println("Running at port 2020...") webcam, _ = gocv.OpenVideoCapture(0) img = gocv.NewMat() http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe(":2020", nil)) webcam.Close() }
完成所有操作后,我可以運(yùn)行Web服務(wù)器并訪問(wèn)localhost:2020以從任何桌面Web瀏覽器查看網(wǎng)絡(luò)攝像頭視頻流。
也可以從我的移動(dòng)網(wǎng)絡(luò)瀏覽器訪問(wèn)該頁(yè)面。
現(xiàn)在,無(wú)論身在何處,我都可以通過(guò)遠(yuǎn)程網(wǎng)絡(luò)攝像頭觀看實(shí)時(shí)視頻。