[Robotics Studio] Sumo[III] - 揪~竟...相撲機器人看到了啥? -- Day21

[Robotics Studio] Sumo[III] - 揪~竟...相撲機器人看到了啥? -- Day21

每次發生了不思議的懸案時, 都是會暫停畫面 (或是縮小為子畫面),
然後跳出一位盛竹如 : "揪~竟..."

讓我們把時間倒回到機器人被推倒之前, 我也很想對機器人問說 "揪~竟...你看到啥?"

就讓我們動手把事實真相展露在世人面前...
(其實如果你在程式執行的時候打開 http://localhost:50000/controlpanel ,
參照之前的 DSS in WebInterface , 你其實可以看到一些東西, PS : Simulated Webcam 的 Web 介面只支援 IE, 不支援 firefox )

還記得之前的 Microsoft.Ccr.Adapters.WinForms  嗎, 有了這東東, 我們隨時可以在 Windows 當中開出視窗, 顯示我們想要看的東西.

所以先把這個 dll 參考到 mysumo 的專案當中, 接著, 新增一個 WinForm , 就叫 CameraImage 好了,
上面放個 PictureBox , 就用預設的名稱 pictureBox1 , 大小至少要超過 176 x 144 (從 Simulated Webcam 偷看來的大小).

image

在 using 當中加入:


using Microsoft.Robotics.Services.mysumo;
using webcam = Microsoft.Robotics.Services.WebCam.Proxy;
using System.Drawing.Imaging;

然後我們就可以加入一個 UpdateImage 的函式:


/// <summary>
/// 更新圖像而且畫出影像辨識結果
/// </summary>
/// <param name="cameraFrame"></param>
/// <param name="result"></param>
public void UpdateImage(webcam.QueryFrameResponse cameraFrame, ImageProcessResult result)
{
    System.Drawing.Bitmap img = new System.Drawing.Bitmap(cameraFrame.Size.Width, cameraFrame.Size.Height, PixelFormat.Format24bppRgb);
    System.Drawing.Imaging.BitmapData bmd = img.LockBits(
        new System.Drawing.Rectangle(0, 0, img.Width, img.Height),
        System.Drawing.Imaging.ImageLockMode.WriteOnly,
        System.Drawing.Imaging.PixelFormat.Format24bppRgb);

    System.Runtime.InteropServices.Marshal.Copy(cameraFrame.Frame, 0, bmd.Scan0, img.Width * img.Height * 3);

    img.UnlockBits(bmd);
    using (Graphics g = Graphics.FromImage(img))
    {
        g.FillRectangle(Brushes.Red, new Rectangle(result.XMean - 10, result.YMean - 10,
            20, 20));
    }
    pictureBox1.Image = img;
}

這個函式的內容就是把 cameraFrame 所傳進來的 data, 轉成 bitmap,
然後在上面畫一個 20x20 的紅色正方形, 標示 ImageProcessResult 的結果
最後再把它設定給 pictureBox1.Image

對了, 因為這個專案預設會產生 XML 說明文件, 而且又把所有警告視為錯誤, 所以你必須要將 public 的 class , function, property 都加上註解, 看你是要改專案設定還是加上註解 (提醒你在 class, function, property 的上一行輸入 /// , 就會自動加上註解), 都可以解決問題.

接著在 mysumo.cs , 先新增一個 cameraimage property , 如下:


private CameraImage cameraform;

你可以對 CameraImage 按右鍵選解析, 會自動幫你加上 using Robotics.mysumo;
然後在 Start() 當中就可以加入 :


WinFormsServicePort.Post(new RunForm(() => cameraform = new CameraImage()));

當然, RunForm 也可以靠自動解析產生 using Microsoft.Ccr.Adapters.WinForms;

最後, 我們就可以修改 ValidateFrameHandler 這個函式, 加入一行就可以, 如下:


// Ignore old images!
if (msFrame < 1000.0)
{
    result = ProcessImage(cameraFrame.Size.Width, cameraFrame.Size.Height, cameraFrame.Frame);
    WinFormsServicePort.FormInvoke(() => cameraform.UpdateImage(cameraFrame, result)); // 加這行!
    double msProcessing = DateTime.Now.Subtract(begin).TotalMilliseconds;
    LogVerbose(LogGroups.Console, string.Format("{0} Frame {1} ms Processed {2} ms", begin, msFrame, msProcessing));

    if (result != null)
    {
        result.TimeStamp = cameraFrame.TimeStamp;
        HandleProcessedImage(result);
    }
    else
    {
        LogVerbose(LogGroups.Console, "Skipping empty camera frame!");
    }
}

在 ProcessImage 之後, 我們就可以更新畫面到 cameraform

另外提一點, 如果你發生問題, 有可能是一個 bug 造成的, 因為我們加了 WinForm, 會導致該 Dll 多了一個 resource, 而原本的 GetEmbedded() 函式是這樣:


/// <summary>
/// Find the embedded file with the specified name and return a stream to read its contents
/// </summary>
/// <returns></returns>
private Stream GetEmbeddedFile()
{
    try
    {
        System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
        string[] names = asm.GetManifestResourceNames();
        if(names.Length > 0)
            return asm.GetManifestResourceStream(names[0]);

        return null;
    }
    catch
    {
        return null;
    }
}

它其實只傳回第一個 resource, 但那不一定是 PlayerImage.bmp (有可能是我們加入的 CameraImage form) , 所以建議改為:


/// <summary>
/// Find the embedded file with the specified name and return a stream to read its contents
/// </summary>
/// <returns></returns>
private Stream GetEmbeddedFile()
{
    try
    {                
        System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
        List<string> lstnames = new List<string>(asm.GetManifestResourceNames());
        string bmpname = lstnames.Find(n => n.EndsWith("playerimage.bmp", StringComparison.InvariantCultureIgnoreCase));
        if(bmpname != null)
            return asm.GetManifestResourceStream(bmpname);

        return null;
    }
    catch
    {
        return null;
    }
}

這樣才會找到 playerimage.bmp 這個資源.
(其實我比較建議用專案的內容去產生資源檔案, 這樣 vs2008 都會幫你搞定好存取資源相關的程式, 簡單又不會出包...)

歐歐, 還有, 寫程式養成歸還資源的好習慣, 所以在 DropHandler 當中加入


if (cameraform != null)
{
    WinFormsServicePort.FormInvoke(() =>
    {
        cameraform.Dispose();
        cameraform = null;
    });
}

這樣就可以在機器人被移除的當下, 關掉 cameraform.

最後啟動 SimulatedSumoReferee, 加入 mysumo 就可以看到畫面啦:

image

在比賽的過程中, 你就會知道機器人看到啥, 而且還會知道影像辨識最後認定敵人的位置...

最後還是要學盛竹如:

李組長皺了皺眉知道這案情必定不單純...
難道冥冥之中, mysumo 相撲機器人註定要打贏這場仗?
揪~竟, mysumo 會不會得到最終的勝利呢, 我們下回分曉...