決定不要繼續拖稿,
這次來跟大家講如何把家裡的WebCam變成監視錄影機,
然後手機能夠即時real - time地看到WebCam的影像喔!!!
小編先跟大家說明實驗環境囉
1. 有一台WebCam的電腦, PC or Mac皆可 (本實驗以Mac為例, Server 語言為C++)
2. iPhone (這次架構需要把程式碼放在apple裝置上...如果沒有的話...還請大家多擔待了!)
本次範例都使用Xcode進行開發, opencv版本需使用2.4.3
(對使用Xcode開發C++ 以及OpenCV有疑問的, 參考這篇連結)
開發環境:
1. Mac OS X 10.8以上
2. Xcode 4.5~4.6 以上 (涵蓋Xcode 5)
(12/29更動: OS X 10.9編譯如果有問題需要參考這篇: OpenCV OS X 編譯失敗解法)
3. Xcode Command Line Tool
開發框架核心:
1. Server端 : OpenCV
2. Client端: CoreFundation
那麼, 廢話不多說, 我們馬上開始這趟旅程吧!!!
[Server端程式架構]
首先先以Xcode開啓一個Command Line Tool專案 (不知道如何開啓的請點這篇)
並且把它匯入OpenCV的library (不清楚做法的同樣請點這篇)
這次我們要透過的是Wifi網路連線, 需要的C++ header檔案包含以下內容
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <string>
#include <pthread.h>
#include <opencv2/core/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
好吧小編知道這內容確實有點多.... 不過這些東西基本上都有用的!!!!
現在說明一下架構:
我們開放port 9899作為視訊連線的功能
此次因為為測試範例, 所有內容都限定在區網(Local NetWork), 還有簡化許多現實中最困難的網路連線問題!
接下來請大家稍微看一下連線的架構圖!
只是用最單純的網路連線架構來說明這範例怎麼進行的! 如果對底層API的使用有興趣的, 希望這篇教學能幫助到你!
那看到上圖, 首先我們的架構就是先寫一個Server出來啦
這次的概念其實就是所謂的Motion JPEG概念 (不清楚的可以參考Codec一文介紹!)
也就是說, 透過截取每一個從攝影機取得的frame,
轉換成一個影像矩陣(Matrix)後,
再把它轉換儲存為jpeg檔案格式傳送出去,
採用這樣的做法優點在於
動態的傳輸jpeg方式可以有效壓縮每一個frame的大小,
提高傳輸效率
(Bear :講這麼文青, 實際上就是傳比較快啦!!!!
不然你的iPhone可能無法連續解析這麼高的資料量,
最後有可能只傳完一張圖片後就動不了了)
也因為是操作矩陣的關係, 我們可以指定圖片檔案矩陣的大小如下:
(定為寬度640, 高度 480, 參考code 39 - 40行)
width = 640;
height = 480;
(詳細程式碼內容會在最後附在下載連結, 這邊僅用圖片呈現!)
開port為9899 (大家也可以隨意指定喔, 不過app部分就要修改了!)
capture.open(0); 代表開始打開攝影機
利用capture.isOpened()判斷攝影機是否能夠成功被打開,
失敗的話直接quit結束程式!
把capture的資料持續寫入到img (也就是 capture >> img 的功用)
並宣告一個一樣大的矩陣img1,
最後重點在使用pthread_create建立一個新thread執行傳輸影像的功用!!
thread_s執行 void* streamServer(void* arg); 的內容!!
接下來我們看thread_s的內容
Step 1. 架設Server 資訊(綁定local ip, 設定thread特性等)
(程式碼內容 : 開啓資料夾ServerCV底下專案)
socket()建立listenSock
設定ip位址給serverAddr
bind ip位址資訊給listenSock
以及設定listenSock能接收最多5個連線資訊
比較重要事imgSize, 因為我們在把影像資料傳輸出去時是一個一維矩陣資料
也就是說它必須同時涵蓋到整個矩陣的內容
這裡的imgSize相當於預先宣告矩陣有多大
所以才會是 img1.total() * img1.elemSize();
接下來進行傳送的架構大致如下圖
之後用accept的方式確認server與client間的連線是否還存在
接下來就是最重要的傳輸檔案部分了!
因為我們是動態的傳輸每一個包裝為jpeg檔案的frame出去
在三方交握中, 必須要讓對方知道檔案大小, 否則另一方以TCP連線情況會不知道該傳輸多少
以HTTP的格式, 會有一個欄位 Contents: 告知檔案大小
但是因為Bear只想簡單的測試這個範例能否順利運行
所以是用偷吃步的方法先send一次 檔案大小的數值出去 (Client端接收檔案大小)
之後再send實際上要傳輸過去的jpeg檔案內容,
這樣client端才能收到正確的圖檔解析出來!
那實際上程式是怎麼運行的呢?
用accept去建立每一次的連線資料, 一旦建立連線失敗直接結束程式
(注意: accept會阻擋程式的運行, 一直到接收到資料或其他狀況才會往下執行)
確認連線建立後
用imencode把資料流img1 壓縮成jpg檔案給 buff
第一個send把buff的資料流長度傳輸出去, 因為是數值資料, 所以檔案大小也就是uint32_t (4byte)
第二個send則根據buff的資料流長度, 把buff給傳輸出去!
到這邊為止就是完成了整個連線行為了!!!
完成單一request後, 之後重新進入到accept -> send -> accept - > send .... 的邏輯
這邊結束後, 我們就可以來進行Client端的部分囉!
Step 2. 架設Client 資訊(接收Server的影像資料)
(程式碼內容 : 開啓資料夾SHWebCameraSocket底下專案)
(新手不知如何建立iOS 專案者請點這篇)
這裡小編設立的是一個readStream以及writeStream的型別
此處小編要提的重點是ip的設定, 這個的ip需要是電腦端的ip,
Mac的用戶可以到 系統偏好設定> 網路 中看到相關的數值
(參考下圖)
如果要做對外連線,
也就是如果你人不在家中的話, 就需要一些修改ip為public外
還有一些網路關於NAT的問題, 因為比較複雜小編就先不在這裡介紹....
iOS做streaming 串流最簡單的方式就是透過程式碼中36 - 52行的內容進行
而實作呈現在畫面上的委派內容則如下:
這理由在於streaming傳來的資料會因為部分漏失資訓或是處理不及下造成無法順利解析
所以我們必須要判斷傳來的data格式是否遵循jpeg檔案的規範
其中jpeg檔案格式規範的重點就在於開頭為FFD8, 結尾為FFD9
符合此格式者即為jpeg檔案!
在- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode 這項委派中
我們進行以下處理
A. 取得將要處理的jpeg檔案大小
uint8_t buff[4];
[(NSInputStream *)aStream read:buff maxLength:4]; // Read Length
uint32_t dataLength = (buff[0] << 8) |(buff[1] << 8) |(buff[2] << 8) | buff[3];
這邊輸入maxLength 4 是因為我們已經知道數字大小是4byte, 所以可以先給常數定值
之後再把得到的buff資料解析為dataLength, 此為jpeg檔案大小
B. 處理jpeg檔案
同樣利用read: maxLength: 去接收tcp傳輸到的檔案內容
接收到以後把它接在imgData上
dataLength = [(NSInputStream *)aStream read:tcpbuff maxLength:dataLength]; // Read Data
[imgdata appendBytes:(const void *)tcpbuff length:dataLength];
銜接完成後判斷檔案格式是不是jpeg, 是的話就把它顯示在畫面上!!
if ([self dataIsValidJPEG:imgdata]) {
[_imgView setImage:[UIImage imageWithData:imgdata]];
}
到這篇整個程式教學結束囉!!
12/29更新: 檔案更新至github上: https://github.com/shouian/SHWebCamExample.git
依照以下步驟
1. 開啓ServerCV資料夾內專案, 啟動程式, 應當會像最上面的圖一樣, 會顯示目前攝影機抓到的內容畫面在電腦上
2. 確認電腦目前ip位址
3. 確認手機與電腦目前是屬於同一區域網路
4. 調整ip位址後, 直接編譯進行程式, 就能跟首頁一樣, 直接進行程式囉!!
對於此次範例有任何問題, 歡迎來信討論!!
喜歡這篇教學的話也歡迎加入Takobear粉絲團獲得更多最新資訊!