【MATLAB】從 IP camera 擷取影像

  • 2629
  • 0
  • 2019-06-19

用 MATLAB 讀取網路攝影機,真的是件超級麻煩的事情。
表面上是有一個好用的 ipcam 函數,但是限制太多。

※ 本文於 2019/06/19 更新程式碼及部分內文 

若您想要使用 MATLAB 讀取網路攝影機的話,
那麼最容易找到的方法應該是使用 ipcam 這個函數。

首先要到 Add-on 下載相關套件:MATLAB Support Package for IP Camera

(警告:若您使用正版的 MATLAB ,從 Add-On 下載套件的任何後果請自行負責。)

該套件的用法如下:

cam = ipcam('URL')
cam = ipcam('URL', 'Username', 'Password')
cam = ipcam('URL', 'Username', 'Password', 'Timeout', Timeout)

但是坦白說,這個函數很難用,除了要先去下載安裝包就算了,
重點是這個函數不能吃 「h.264」 的影像串流,只能夠接受「mjpeg」的格式。

夏恩從維基百科擷取一段文字過來:

H.264,又稱為MPEG-4第10部分,是一種面向塊的基於運動補償的影片編碼標準。
到 2014年,它已經成為高精度影片錄製、壓縮和發布的最常用格式之一。


H.264這麼好,然後MATLAB說不能吃!開什麼玩笑!
好吧,夏恩的版本是R2016b,說不定之後的版本已經解決了這個問題。

※ 2019.06.19 更新資訊:R2019a 仍然沒有解決這個問題。※

所以,為了解決這個問題,只好自己設計一個擷取 ipcam 的程式了!
以下使用 C# 和 EmguCV 寫一個擷取程式,再把資料送給 MATLAB 用。

環境介紹:
編譯器:Visual Studio 2015 community (註冊電子信箱就可以用)
專案類型:類別庫 (請不要忽略這句話)
目標Framework:.NET Framework 4.5.2
EmguCV版本:emgucv-windesktop 4.0.1.3373
目標CPU:x64 (有些編譯器預設Any CPU,從組態管理員進去改)
( 註:EmguCV的載點在這裡:https://sourceforge.net/projects/emgucv/ )

Step 1:安裝 EmguCV

EmguCV 的安裝很簡單,一直按「下一步」就好。

安裝完畢後,請新增兩個環境變數:

C:\Emgu\emgucv-windesktop 4.0.1.3373\bin
C:\Emgu\emgucv-windesktop 4.0.1.3373\libs\x64

Step 2:建立 C# 類別庫

夏恩先創建一個類別庫,名稱叫做 lib_test

創建類別後,加入兩個參考:

1. Emgu.CV.World.dll
     該檔案的位置在:C:\Emgu\emgucv-windesktop 4.0.1.3373\bin\Emgu.CV.World.dll

2. System.ServiceModel.dll
     這個就比較難找了,以夏恩為例,路徑如下:
     C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\

再來是撰寫程式:

// C# code
// 說明:擷取 ipcam 影像供 MATLAB 使用
//
// 2019.06.19 Shayne

using System;
using Emgu.CV;
using Emgu.CV.Structure;

namespace ipcamPKG
{
    public class ipcam
    {
        private Mat _frame = new Mat();
        private VideoCapture _ipcam = null;

        public Image<Bgr, byte> frame = null;
        public int Height, Width;
        public bool status = false;
        public string ErrorMessage = "";

        public ipcam(string URL)
        {
            try
            {
                _ipcam = new VideoCapture(URL);
                if (_ipcam.IsOpened)
                {
                    Height = _ipcam.Height;
                    Width = _ipcam.Width;

                    frame = new Image<Bgr, byte>(Height, Width);

                    status = true;
                    _ipcam.ImageGrabbed += ProcessFrame;

                    // 若沒有加入 System.ServiceModel.dll 的話,則無法成功啟動攝影機
                    _ipcam.Start(); 

                }
            }
            catch (Exception e)
            {
                ErrorMessage = e.Message;
                status = false;
            }
        }

        public void snapshot()
        {
            using (Image<Bgr, byte> temp = _frame.ToImage<Bgr, byte>())
            {
                try
                {
                    frame = temp.Clone();
                }
                catch (Exception e)
                {
                    ErrorMessage = e.Message;
                    status = false;
                }
            }
        }

        public void cvRelease()
        {
            _ipcam.Stop();
            GC.Collect();
        }

        private void ProcessFrame(object sender, EventArgs arg)
        {
            _ipcam.Retrieve(_frame, 0);
        }
    }
}

寫完之後,按下建置專案,就能把程式編譯成類別庫,也就是 「.dll」 檔案類型。
得到類別庫檔案後,就可以在 MATLAB 內使用了!

在本範例中,建置完畢後您可以在 「.\ipcamPKG\bin\x64\Debug\」中找到 ipcamPKG.dll 檔案。

Step 3:撰寫 MATLAB 程式

把剛才編譯好的檔案 ipcamPKG.dll,連同 Emgu.CV.World.dll ,一起放到同一個地方。

MATLAB​ 所需相關工具箱:
  MATLAB Compiler™ Toolbox
  MATLAB Compiler SDK™ Toolbox

% MATLAB code
% 說明:擷取 ipcam 示範程式
%
% 2019.06.19 Shayne

% Import .NET assembly 
% 自定義類別要使用絕對路徑。
if exist([pwd, '\ipcamPKG.dll'], 'file')
    NET.addAssembly( [pwd, '\ipcamPKG.dll'] );
else
    uiwait(msgbox('ipcamPKG loading error!', 'ERROR', 'error'))
    return
end

% 每間攝影機廠商的 URL 會不一樣,請直接詢問廠商正確的 URL。
URL = 'rtsp://admin:admin@192.168.1.1/video.h264';

% 宣告自定義類別
% ipcamPKG 就是剛才自定義的 namespace
% ipcam 就是自定義的類別名稱
camera = ipcamPKG.ipcam(URL); 
pause(1);

for i=1:10 % 擷取 10 次影像
    
    % calling snapshot method
    camera.snapshot();
   
    % get image
    I = flip(uint8(camera.frame.Data), 3);
    
    % show image
    imshow(I)

    % 請注意:
    % 若間隔時間若設定太短,容易會造成記憶體無法回收,導致電腦故障
    pause(0.1)
end

% close the ipcam
camera.cvRelease();

搞定!如此一來,我們就能透過 C# 使得 MATLAB 可以擷取 H264 串流格式了!
感覺不賴吧!

小結

最後來討論幾個夏恩自己遇到的問題。

Q1:攝影機可以不要在建立物件時啟動嗎?我想手動開關!
Ans:

建議不要,常會有問題。
若直接在 MATLAB 中使用 VideoCapture 物件的方法 start(),只能抓到一幀影像且不會更新。

Q2:為什麼要用 ImageGrabbed 事件?用 Grab 或是 QueryFrame 方法可以嗎?
Ans:

可以,但是會有影像延遲的問題。
攝影機一旦啟動,就會一直把資料送過來。
若讓影像在 buffer 內堆積的話,會導致影像產生延遲,類似的問題與討論請看以下:

1. EmguCV/OpenCV QueryFrame slow/buffers
2. 【Python】改善 VideoCapture 的影像延遲

Q3:為什麼要定義 frame 變數?讓函數直接回傳影像就好啦!
Ans:

就夏恩的經驗來看,直接讀取變數資料的程式寫法比較簡單。
有關 C# 與 MATLAB 影像交換的討論,請看夏恩的另外一篇文章:
【Mixing】在 C# 與 MATLAB 間交換影像資料

當然了,若您想使用回傳影像的方式也是可以,請自行嘗試看看!OK的。