QT+OpenCV实现录屏功能

 更新时间:2022年01月19日 15:15:18   作者:平凡的编程者  
这篇文章主要为大家详细介绍了QT+OpenCV实现录屏功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文使用QT+opencv来实现对指定窗体画面录制,并保存为avi文件。

(1)获取窗体界面

QScreen类有一个grabWindow函数,可以用来获取窗体的画面,这个函数使用很简单,就是传入窗体句柄和要截取的坐标。但是这个函数有一个缺陷,它是通过截取桌面画面的方式,而不是通过

窗体获取界面,所以当你的窗体被其他窗体遮挡时,就无法截取完整的窗体界面,如果你是要录制整个桌面画面,那用这个函数就可以了,下面的方法调用GDI函数来实现,即使窗体被遮挡时仍然能够获取到完整界面,但是窗体最小化时也一样无法获取。

/*
*    函数功能:获取窗体指定窗体图像
*   参      数:hd:窗体句柄
*                 pm:保存获取到的图片
*                 x:截取的起始x坐标,
*                 y:截取的起始y坐标,
*                 w:截取的宽度
*                 h:截取的高度
*/
bool GetGDIBitmap(HWND hd,QPixmap &pm, int x, int y, int w, int h)
{
    if(hd==NULL)
        return false;
    HDC hDC;
    hDC=GetDCEx(hd,NULL,DCX_PARENTCLIP );
    HDC hMemDC;                    //内存缓冲设备环境
    HBITMAP hbmMem,hbmOld;        //内存缓冲设备环境中的位图
    RECT rc;
    rc.left=x;
    rc.top=y;
    rc.right=x+w;
    rc.bottom=y+h;
    //判断边境值
    RECT clientrc;
    ::GetClientRect(hd,&clientrc);

    int xc =0;
    int cx =0;
    int cy =0;

    if(rc.bottom>clientrc.bottom || rc.bottom<0)
        rc.bottom=clientrc.bottom;

    if(rc.right>clientrc.right || rc.right<0)
        rc.right=clientrc.right;

    // 24位图的BITMAPINFO
    BITMAPINFO *pBITMAPINFO = (BITMAPINFO*)malloc(sizeof(BITMAPINFOHEADER));
    memset(pBITMAPINFO, 0, sizeof(BITMAPINFOHEADER));
    BITMAPINFOHEADER *pInfo_Header = (BITMAPINFOHEADER *)pBITMAPINFO;
    pInfo_Header->biSize = sizeof(BITMAPINFOHEADER);
    pInfo_Header->biWidth = rc.right - rc.left;
    pInfo_Header->biHeight = (rc.bottom - rc.top);
    pInfo_Header->biPlanes = 1;
    pInfo_Header->biBitCount = 24;
    pInfo_Header->biCompression = BI_RGB;

    hMemDC=CreateCompatibleDC(hDC);    //创建内存兼容设备环境
    //创建内存兼容位图
    hbmMem=CreateCompatibleBitmap(hDC,pInfo_Header->biWidth,pInfo_Header->biHeight);

    hbmOld=(HBITMAP)SelectObject(hMemDC,hbmMem);
    //将内存设备环境中的内容绘制到物理设备环境   hDC
    BitBlt(hMemDC,0,0,pInfo_Header->biWidth,pInfo_Header->biHeight,hDC,cx+rc.left,xc+cy+rc.top,CAPTUREBLT|SRCCOPY);
    HBITMAP hBitmap=(HBITMAP)SelectObject(hMemDC,hbmOld);

    // 获得数据buf
    DWORD bufSize=(pInfo_Header->biWidth * 3 + 3) / 4 * 4 * pInfo_Header->biHeight;
    BYTE * pBuffer = new BYTE[bufSize];

    int aHeight=pInfo_Header->biHeight;

    if(::GetDIBits(hMemDC, hBitmap, 0, aHeight, pBuffer,pBITMAPINFO, DIB_RGB_COLORS) == 0)
    {
        return false;
    }

    bool bret=BitmapToPixmap(hBitmap,pm);


    ReleaseDC(hd,hDC);
    //释放资源
    DeleteObject(hbmMem);
    DeleteObject(hbmOld);
    DeleteDC(hMemDC);
    free(pBITMAPINFO);
    ::DeleteObject(hBitmap);
    delete [] pBuffer;
    return bret;
}    

/*
*    函数功能:将bitmap转为QPixmap
*/
bool BitmapToPixmap(HBITMAP hBitmap, QPixmap &pm)
{
    HDC     hDC;
    //设备描述表
    int     iBits;
    //当前显示分辨率下每个像素所占字节数
    WORD    wBitCount;
    //位图中每个像素所占字节数
    //定义调色板大小, 位图中像素字节大小 ,  位图文件大小 , 写入文件字节数
    DWORD           dwPaletteSize=0,dwBmBitsSize,dwDIBSize;
    BITMAP          Bitmap;
    //位图属性结构
    BITMAPFILEHEADER   bmfHdr;
    //位图文件头结构
    BITMAPINFOHEADER   bi;
    //位图信息头结构
    LPBITMAPINFOHEADER lpbi;
    //指向位图信息头结构
    HANDLE          hDib, hPal;
    HPALETTE     hOldPal=NULL;
    //定义文件,分配内存句柄,调色板句柄

    //计算位图文件每个像素所占字节数
    hDC = CreateDC(L"DISPLAY",NULL,NULL,NULL);
    iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
    DeleteDC(hDC);
    if (iBits <= 1)
        wBitCount = 1;
    else if (iBits <= 4)
        wBitCount = 4;
    else if (iBits <= 8)
        wBitCount = 8;
    else if (iBits <= 24)
        wBitCount = 24;
    else
        wBitCount = 24;
    //计算调色板大小
    if (wBitCount <= 8)
        dwPaletteSize=(1<<wBitCount)*sizeof(RGBQUAD);

    //设置位图信息头结构
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap);
    bi.biSize            = sizeof(BITMAPINFOHEADER);
    bi.biWidth           = Bitmap.bmWidth;
    bi.biHeight          = Bitmap.bmHeight;
    bi.biPlanes          = 1;
    bi.biBitCount         = wBitCount;
    bi.biCompression      = BI_RGB;
    bi.biSizeImage         = 0;
    bi.biXPelsPerMeter     = 0;
    bi.biYPelsPerMeter     = 0;
    bi.biClrUsed           = 0;
    bi.biClrImportant      = 0;

    dwBmBitsSize = ((Bitmap.bmWidth*wBitCount+31)/32)*4*Bitmap.bmHeight;
    //为位图内容分配内存
    hDib  = GlobalAlloc(GHND,dwBmBitsSize+dwPaletteSize+sizeof(BITMAPINFOHEADER));
    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
    *lpbi = bi;
    // 处理调色板
    hPal = GetStockObject(DEFAULT_PALETTE);
    if (hPal)
    {
        hDC = ::GetDC(NULL);
        hOldPal=SelectPalette(hDC,(HPALETTE)hPal,FALSE);
        RealizePalette(hDC);
    }
    // 获取该调色板下新的像素值
    GetDIBits(hDC,hBitmap,0,(UINT)Bitmap.bmHeight,(LPSTR)lpbi+sizeof(BITMAPINFOHEADER)+dwPaletteSize, (BITMAPINFO *)lpbi,DIB_RGB_COLORS);
    //恢复调色板
    if (hOldPal)
    {
        SelectPalette(hDC, hOldPal, TRUE);
        RealizePalette(hDC);
        ::ReleaseDC(NULL, hDC);
    }
    // 设置位图文件头
    bmfHdr.bfType = 0x4D42;  // "BM"
    dwDIBSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+dwPaletteSize+dwBmBitsSize;
    bmfHdr.bfSize = dwDIBSize;
    bmfHdr.bfReserved1 = 0;
    bmfHdr.bfReserved2 = 0;
    bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER)+(DWORD)sizeof(BITMAPINFOHEADER)+dwPaletteSize;

    std::vector<uchar>buffer;
    uchar *p=(uchar*)&bmfHdr;
    // 写入位图文件头
    buffer.insert(buffer.end(),p,p+sizeof(BITMAPFILEHEADER));
    // 写入位图文件其余内容
    p=(uchar*)lpbi;
    buffer.insert(buffer.end(),p,p+sizeof(BITMAPINFOHEADER)+dwPaletteSize+dwBmBitsSize);
    //清除
    GlobalUnlock(hDib);
    GlobalFree(hDib);
    pm=QPixmap::fromImage(QImage::fromData(buffer.data(),buffer.size()));
    return true;
}

(2)录制画面

bool g_needstop =false;void Record()
{
     RECT rect;
     //获取窗体位置大小
     GetWindowRect(hd,&rect);
     cv::Size frameSize;
     frameSize.width=rect.right-rect.left;
     frameSize.height=rect.bottom-rect.top;   
     cv::VideoWriter VideoWriter;
     if(!VideoWriter.open("d:\\1.avi",CV_FOURCC('M', 'J', 'P', 'G'),40,frameSize))
         return;
    while(!g_needstop)
    {
        QPixmap pm;
        GetGDIBitmap(hd,pm,0,0,frameSize.width,frameSize.height);
        VideoWriter.write(ImageToMat(pm.toImage()));
    }        VideoWriter.release(); 
}

Mat ImageToMat(QImage img,QString imgFormat)
{
    if(img.isNull())
        return Mat();
    QByteArray ba;
    QBuffer buffer(&ba);
    buffer.open(QIODevice::WriteOnly);
    img.save(&buffer,imgFormat.toLatin1().data());
    _InputArray arrSrc(ba.data(), ba.size());
    Mat mat = cv::imdecode(arrSrc, CV_LOAD_IMAGE_COLOR);
    return mat;
}

(3) 播放视频

void Play()
{
    cv::VideoCapture Capture;
    if(!Capture.open("d:\\1.avi"))
          return;
    Mat frame;
    //逐帧读取画面
    while(Capture.read(frame))
    {
            //转成QImage格式用于显示
           QImage img = MatToImage(frame);
           emit Frame(img);
           QThread::msleep(40);
    }
    Capture.release();
    emit PlayFinsh();
}

QImage MatToImage(Mat mat)
{
    if(mat.type() == CV_8UC1)
    {
        QImage image(mat.cols, mat.rows, QImage::Format_Indexed8);
        // Set the color table (used to translate colour indexes to qRgb values)
        image.setColorCount(256);
        for(int i = 0; i < 256; i++)
        {
            image.setColor(i, qRgb(i, i, i));
        }
        // Copy input Mat
        uchar *pSrc = mat.data;
        for(int row = 0; row < mat.rows; row ++)
        {
            uchar *pDest = image.scanLine(row);
            memcpy(pDest, pSrc, mat.cols);
            pSrc += mat.step;
        }
        return image;
    }
    // 8-bits unsigned, NO. OF CHANNELS = 3
    else if(mat.type() == CV_8UC3)
    {
        // Copy input Mat
        const uchar *pSrc = (const uchar*)mat.data;
        // Create QImage with same dimensions as input Mat
        QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_RGB888);
        return image.rgbSwapped();
    }
    else if(mat.type() == CV_8UC4)
    {
        qDebug() << "CV_8UC4";
        // Copy input Mat
        const uchar *pSrc = (const uchar*)mat.data;
        // Create QImage with same dimensions as input Mat
        QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_ARGB32);
        return image.copy();
    }
    else
    {
        qDebug() << "ERROR: Mat could not be converted to QImage.";
        return QImage();
    }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • C语言实现第一次防死版扫雷游戏

    C语言实现第一次防死版扫雷游戏

    大家好,本篇文章主要讲的是C语言实现第一次防死版扫雷游戏,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • C++中的作用域案例详解

    C++中的作用域案例详解

    作用域规定了标识符在代码中的可见性和可访问性,全局作用域中的标识符可以在整个程序中使用,局部作用域中的标识符只能在其所在的代码块中使用,而命名空间作用域提供了一种组织和封装代码的方式,以避免命名冲突,这篇文章主要介绍了C++中的作用域,需要的朋友可以参考下
    2024-02-02
  • C++多重继承引发的重复调用问题与解决方法

    C++多重继承引发的重复调用问题与解决方法

    这篇文章主要介绍了C++多重继承引发的重复调用问题与解决方法,结合具体实例形式分析了C++多重调用中的重复调用问题及相应的解决方法,需要的朋友可以参考下
    2018-05-05
  • 详解C++中的内存同步模式(memory order)

    详解C++中的内存同步模式(memory order)

    这篇文章主要介绍了C++中的内存同步模式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • C++中的伪随机数

    C++中的伪随机数

    这篇文章主要介绍了C++中的伪随机数,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • C++实现LeetCode(211.添加和查找单词-数据结构设计)

    C++实现LeetCode(211.添加和查找单词-数据结构设计)

    这篇文章主要介绍了C++实现LeetCode(211.添加和查找单词-数据结构设计),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • Qt编程实现小时钟

    Qt编程实现小时钟

    这篇文章主要为大家详细介绍了Qt编程实现小时钟,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • C++如何采用Daemon进行后台程序的部署

    C++如何采用Daemon进行后台程序的部署

    这篇文章主要介绍了C++采用Daemon进行后台程序的部署,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-04-04
  • C++的缺省参数你了解嘛

    C++的缺省参数你了解嘛

    这篇文章主要为大家介绍了C++缺省参数,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • Qt数据库应用之超级自定义委托

    Qt数据库应用之超级自定义委托

    Qt中需要用到自定义委托的情形很多,比如提供下拉框选择,进度条展示下载进度啥的,默认的单元格是没有这些效果的,需要自己单独用委托的形式来展示。本文将为大家介绍Qt中如何进行超级自定义委托,需要的可以参考一下
    2022-03-03

最新评论