前言

  执行程序是用wpf开发的,所以先将opencv封装成c语言接口,以供调用。opencv也不可能提供现成的控件供wpf使用,两种不同的开发语言“沟通”起来有些困难。其实稍作变通,就可以实现摄像头播放功能。

1 对opencv封装

opencv的类VideoCapture封装了对摄像头的操作,使用起来也非常简单。

 bool open(int device); device为摄像头设备序号。

如果有多个摄像头,怎么知道哪个摄像头的序号那?可以通过如下函数,获取摄像头列表。摄像头在list中索引即为设备序号。



int GetCameraDevices(vector<wstring>& list)
{
    ICreateDevEnum *pDevEnum = NULL;
    IEnumMoniker *pEnum = NULL;
    int deviceCounter = 0;
    CoInitialize(NULL);
    HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
        CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
        reinterpret_cast<void**>(&pDevEnum));

    if (SUCCEEDED(hr))
    {
        // Create an enumerator for the video capture category.
        hr = pDevEnum->CreateClassEnumerator(
            CLSID_VideoInputDeviceCategory,
            &pEnum, 0);

        if (hr == S_OK) {

            //if (!silent)printf("SETUP: Looking For Capture Devices\n");
            IMoniker *pMoniker = NULL;

            while (pEnum->Next(1, &pMoniker, NULL) == S_OK) {

                IPropertyBag *pPropBag;
                hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,
                    (void**)(&pPropBag));

                if (FAILED(hr)) {
                    pMoniker->Release();
                    continue;  // Skip this one, maybe the next one will work.
                }

                // Find the description or friendly name.
                VARIANT varName;
                VariantInit(&varName);
                hr = pPropBag->Read(L"Description", &varName, 0);

                if (FAILED(hr))
                    hr = pPropBag->Read(L"FriendlyName", &varName, 0);

                if (SUCCEEDED(hr)) {

                    hr = pPropBag->Read(L"FriendlyName", &varName, 0);

                    int count = 0;
                    wstring str2 = varName.bstrVal;
                    list.push_back(str2);
                }

                pPropBag->Release();
                pPropBag = NULL;

                pMoniker->Release();
                pMoniker = NULL;

                deviceCounter++;
            }

            pDevEnum->Release();
            pDevEnum = NULL;

            pEnum->Release();
            pEnum = NULL;
        }
    }
    return deviceCounter;
}



总之,使用opencv打开摄像头非常简单。

打开之后,就是获取摄像头图像。视频其实就是图像的集合;每秒钟获取25幅图像,将其在控件上显示,就是视频。



Mat cameraImg;
_pCapture >> cameraImg;



Mat类封装了对图像的操作。c#不可能操作Mat,需要将Mat中纯图像部分数据传递出来,图像才能被c#利用。



int Camera_GetImgData(INT64 handle, char* imgBuffer)
{
    CameraInfo *pCameraInfo = (CameraInfo*)handle;

    Mat cameraImg;
    *(pCameraInfo->_pCapture) >> cameraImg;

    if (!cameraImg.empty())
    {
        int height = cameraImg.rows;int dataLen = height * cameraImg.step;
        memcpy(imgBuffer, cameraImg.data, dataLen);

        return 0;
    }
    else
    {
        return 1;
    }
}



cameraImg.data中存有图像数据,data的大小可以根据图像的高度、每行图像的步幅计算出来。c#调用此函数后,imgBuffer存放图像数据。对数据imgBuffer处理后,就可以在控件上显示。

c语言对opencv封装函数列表如下:



extern "C"
{
    OpenCVCamera_API int Camera_GetCameraName(char* listName);

    OpenCVCamera_API INT64 Camera_CreateHandle();
    OpenCVCamera_API void Camera_CloseHandle(INT64 handle);

    OpenCVCamera_API BOOL Camera_IsOpen(INT64 handle);
    OpenCVCamera_API int Camera_Open(INT64 handle, int index);
    OpenCVCamera_API int Camera_Close(INT64 handle);

    OpenCVCamera_API int Camera_GetImgInfo(INT64 handle,int& width,int& height,int& channel,
        int& step, int& depth);
    OpenCVCamera_API int Camera_GetImgData(INT64 handle, char* imgBuffer);

    //flipCode >0: 沿y-轴翻转, 0: 沿x-轴翻转, <0: x、y轴同时翻转
    OpenCVCamera_API int Camera_GetImgData_Flip(INT64 handle, char* imgBuffer, int flipCode);
    OpenCVCamera_API int Camera_ImgData_Compress(int rows, int cols, int type, void* imgBuffer,
        int param,void* destBuffer,int* destLen);
}



2 WPF实现视频播放

WPF的Image控件实现图像的显示。实现视频播放的逻辑为:设定一个定时器(时间间隔为40毫秒),每隔一段时间从opencv获取图像,在控件中显示。



<Image x:Name="imageVideoPlayer" Stretch="Uniform" ></Image>



实现图像显示代码



BitmapSource bitmapSource = _openCVCamera.GetBitmapSource();
            if (bitmapSource == null)
                return false;

 imageVideoPlayer.Source = bitmapSource;



实现图像显示的关键是构建BitmapSource,暨:如何从opencv中获取图像数据构建BitmapSource。



//获取图像数据
   if (!GetImgData(out byte[] imgData))
                    return null;

   //构建WriteableBitmap
   WriteableBitmap img = new WriteableBitmap(_imgWidth, _imgHeight, 96, 96, PixelFormats.Bgr24, null);
   img.WritePixels(new Int32Rect(0, 0, _imgWidth, _imgHeight),
                   imgData, img.BackBufferStride, 0);
   img.Freeze();



至此,就可以显示摄像头图像了。

Opencv摄像头存在 opencv 摄像头_ide