【问题标题】:c++ fast screenshots in linux for use with opencvlinux中用于opencv的c++快速截图
【发布时间】:2014-09-19 05:20:44
【问题描述】:

我正在尝试寻找一种快速的方法来截取屏幕截图(如 30 fps 或更高),以便在 c++ 中与 opencv 一起使用

我在网上找到的所有信息要么涉及 windows.h,要么太慢。

有人可以提供一些代码来实现这一点,或者至少为我指明正确的方向,以便我自己解决这个问题吗?

【问题讨论】:

    标签: c++ linux performance opencv screenshot


    【解决方案1】:

    您可以使用它来将屏幕截图转换为原始像素结构。将它与 Width & Height & BitsPerPixel 一起传递给 OpenCV,你应该会很好。

    #include <X11/Xlib.h>
    #include <X11/Xutil.h>
    #include <cstdint>
    #include <cstring>
    #include <vector>
    
    void ImageFromDisplay(std::vector<uint8_t>& Pixels, int& Width, int& Height, int& BitsPerPixel)
    {
        Display* display = XOpenDisplay(nullptr);
        Window root = DefaultRootWindow(display);
    
        XWindowAttributes attributes = {0};
        XGetWindowAttributes(display, root, &attributes);
    
        Width = attributes.width;
        Height = attributes.height;
    
        XImage* img = XGetImage(display, root, 0, 0 , Width, Height, AllPlanes, ZPixmap);
        BitsPerPixel = img->bits_per_pixel;
        Pixels.resize(Width * Height * 4);
    
        memcpy(&Pixels[0], img->data, Pixels.size());
    
        XDestroyImage(img);
        XCloseDisplay(display);
    }
    

    然后将其与OpenCV 一起使用,您可以这样做:

    int main()
    {
        int Width = 0;
        int Height = 0;
        int Bpp = 0;
        std::vector<std::uint8_t> Pixels;
    
        ImageFromDisplay(Pixels, Width, Height, Bpp);
    
        if (Width && Height)
        {
            Mat img = Mat(Height, Width, Bpp > 24 ? CV_8UC4 : CV_8UC3, &Pixels[0]); //Mat(Size(Height, Width), Bpp > 24 ? CV_8UC4 : CV_8UC3, &Pixels[0]); 
    
            namedWindow("WindowTitle", WINDOW_AUTOSIZE);
            imshow("Display window", img);
    
            waitKey(0);
        }
        return 0;
    }
    

    【讨论】:

    • 哦,为了加快速度,您可以将 memcpy 替换为对像素图像的 OpenCV api 的调用,或者您需要像素的图像..
    • 您能否提供一个如何将其与 opencv 一起使用的示例? (例如使用 imshow() 显示输出图像)。我有点麻烦。
    • 我用这个编译了上面的代码: g++ main.cpp -std=c++0x -o app 'pkg-config --cflags --libs opencv' 它编译成功了,除非我尝试运行编译后的程序,会导致分段错误。有人知道为什么吗?
    • 您还需要包含 x11 的库,例如 g++ main.cpp -std=c++0x -o app 'pkg-config --cflags --libs opencv x11'
    • 这会泄漏内存。在函数ImageFromDisplay 中使用XDestroyImage(img); 而不是XFree(img);
    【解决方案2】:

    根据@abc 的回答,我想出了以下内容,使用 MIT 的 X 共享内存扩展。

    @abc 的示例以 120-180 fps 的速度运行,并使用了大约 40% 的 Titan X (Maxwell)。以下以 15000 fps 运行并使用 Titan X 的 80%。(假设您不同步。如果您进行同步,那么帧率下降到@abc 的帧率!)

    注释掉内部循环中的break; 语句,也可以不间断地运行它。

    使用@abc 类的版本:

    // g++ screena.cpp -o screena -lX11 -lXext -Ofast -mfpmath=both -march=native -m64 -funroll-loops -mavx2 `pkg-config opencv --cflags --libs` && ./screena
    
    #include <X11/Xlib.h>
    #include <X11/Xutil.h>
    
    #include <X11/extensions/XShm.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    #include <opencv2/opencv.hpp>  // This includes most headers!
    
    #include <time.h>
    #define FPS(start) (CLOCKS_PER_SEC / (clock()-start))
    
    
    struct ScreenShot{
        ScreenShot(uint x, uint y, uint width, uint height):
                   x(x), y(y), width(width), height(height){
    
            display = XOpenDisplay(nullptr);
            root = DefaultRootWindow(display);
    
            XGetWindowAttributes(display, root, &window_attributes);
            screen = window_attributes.screen;
            ximg = XShmCreateImage(display, DefaultVisualOfScreen(screen), DefaultDepthOfScreen(screen), ZPixmap, NULL, &shminfo, width, height);
    
            shminfo.shmid = shmget(IPC_PRIVATE, ximg->bytes_per_line * ximg->height, IPC_CREAT|0777);
            shminfo.shmaddr = ximg->data = (char*)shmat(shminfo.shmid, 0, 0);
            shminfo.readOnly = False;
            if(shminfo.shmid < 0)
                puts("Fatal shminfo error!");;
            Status s1 = XShmAttach(display, &shminfo);
            printf("XShmAttach() %s\n", s1 ? "success!" : "failure!");
    
            init = true;
        }
    
        void operator() (cv::Mat& cv_img){
            if(init)
                init = false;
    
            XShmGetImage(display, root, ximg, 0, 0, 0x00ffffff);
            cv_img = cv::Mat(height, width, CV_8UC4, ximg->data);
        }
    
        ~ScreenShot(){
            if(!init)
                XDestroyImage(ximg);
    
            XShmDetach(display, &shminfo);
            shmdt(shminfo.shmaddr);
            XCloseDisplay(display);
        }
    
        Display* display;
        Window root;
        XWindowAttributes window_attributes;
        Screen* screen;
        XImage* ximg;
        XShmSegmentInfo shminfo;
    
        int x, y, width, height;
    
        bool init;
    };
    
    
    int main(){
        ScreenShot screen(0, 0, 1920, 1080);
        cv::Mat img;
    
        for(uint i;; ++i){
            double start = clock();
    
            screen(img);
    
            if(!(i & 0b111111))
                printf("fps %4.f  spf %.4f\n", FPS(start), 1 / FPS(start));
                break;
    
        }
    
        cv::imshow("img", img);
        cv::waitKey(0);
    }
    

    “裸”版:

    // g++ xshm2.c -o xshm2 -lX11 -lXext `$cv`-Ofast -mfpmath=both -march=native -m64 -funroll-loops -mavx2 && ./xshm2
    
    #include <X11/Xlib.h>
    #include <X11/Xutil.h>
    
    #include <X11/extensions/XShm.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    #include <opencv2/opencv.hpp>  // This includes most headers!
    
    #include <time.h>
    #define FPS(start) (CLOCKS_PER_SEC / (clock()-start))
    
    // Using one monitor DOESN'T improve performance! Querying a smaller subset of the screen DOES
    const uint WIDTH  = 1920>>0;
    const uint HEIGHT = 1080>>0;
    
    // -------------------------------------------------------
    int main(){
        Display* display = XOpenDisplay(NULL);
        Window root = DefaultRootWindow(display);  // Macro to return the root window! It's a simple uint32
        XWindowAttributes window_attributes;
        XGetWindowAttributes(display, root, &window_attributes);
        Screen* screen = window_attributes.screen;
        XShmSegmentInfo shminfo;
        XImage* ximg = XShmCreateImage(display, DefaultVisualOfScreen(screen), DefaultDepthOfScreen(screen), ZPixmap, NULL, &shminfo, WIDTH, HEIGHT);
    
        shminfo.shmid = shmget(IPC_PRIVATE, ximg->bytes_per_line * ximg->height, IPC_CREAT|0777);
        shminfo.shmaddr = ximg->data = (char*)shmat(shminfo.shmid, 0, 0);
        shminfo.readOnly = False;
        if(shminfo.shmid < 0)
            puts("Fatal shminfo error!");;
        Status s1 = XShmAttach(display, &shminfo);
        printf("XShmAttach() %s\n", s1 ? "success!" : "failure!");
    
        cv::Mat img;
    
        for(int i; ; i++){
            double start = clock();
    
            XShmGetImage(display, root, ximg, 0, 0, 0x00ffffff);
            img = cv::Mat(HEIGHT, WIDTH, CV_8UC4, ximg->data);
    
            if(!(i & 0b111111))
                printf("fps %4.f  spf %.4f\n", FPS(start), 1 / FPS(start));
            break;
        }
    
        cv::imshow("img", img);
        cv::waitKey(0);
    
        XShmDetach(display, &shminfo);
        XDestroyImage(ximg);
        shmdt(shminfo.shmaddr);
        XCloseDisplay(display);
        puts("Exit success!");
    }
    

    【讨论】:

    • 嗯...我不太确定,但在这里应该使用clock() 吗?我有疑问,因为 CLOCKS_PER_SEC/clock() 返回的是 CPU 时间而不是挂钟时间(15000 fps 似乎非常大)。虽然当我使用 chrono c++ 库来衡量性能时,您的方法更快,所以我 +1。
    • @abc 我不知道clock!你可能是对的 :) 它快了多少?
    • 我的机器上使用我的代码获得了大约 50 fps,而使用您的代码获得了 120/140 fps。
    • 我知道问题是关于 Linux 的,但是这可以在 Windows 上运行吗?如果没有,是否有类似的方法可以用于 Windows 以达到类似的效率?
    • 这段代码 sn-p 使用 (Xlib)[x.org/releases/X11R7.7/doc/libX11/libX11/libX11.html],这是一个与 (X Window System)[en.wikipedia.org/wiki/X_Window_System] 交互的库。我不知道 Windows 是否可以运行 X Window System,如果可以,我不知道它是否使用 Xlib。但 Windows 肯定有自己的高效 API 来获取屏幕像素...
    【解决方案3】:

    我使用 Brandon 的回答制作了一个更快的基于仿函数的屏幕截图代码:

    #include <opencv2/opencv.hpp>
    #include <X11/Xlib.h>
    #include <X11/Xutil.h>
    
    class ScreenShot
    {
        Display* display;
        Window root;
        int x,y,width,height;
        XImage* img{nullptr};
    public:
        ScreenShot(int x, int y, int width, int height):
            x(x),
            y(y),
            width(width),
            height(height)
        {
            display = XOpenDisplay(nullptr);
            root = DefaultRootWindow(display);
        }
    
        void operator() (cv::Mat& cvImg)
        {
            if(img != nullptr)
                XDestroyImage(img);
            img = XGetImage(display, root, x, y, width, height, AllPlanes, ZPixmap);
            cvImg = cv::Mat(height, width, CV_8UC4, img->data);
        }
    
        ~ScreenShot()
        {
            if(img != nullptr)
                XDestroyImage(img);
            XCloseDisplay(display);
        }
    };
    

    下面是你如何在循环中使用它(按q退出):

    int main()
    {
        ScreenShot screen(0,0,1920,1080);
        cv::Mat img;
    
        while(true) 
        {
            screen(img);
    
            cv::imshow("img", img);
            char k = cv::waitKey(1);
            if (k == 'q')
                break;
        }
    }
    

    这在我的机器上快了大约 39%。

    【讨论】:

    • 这在我的电脑上实际上快了 3 倍!感谢分享
    【解决方案4】:
    #include <X11/Xlib.h>
    #include <X11/Xutil.h>
    #include <opencv2/opencv.hpp>
    #include <opencv2/highgui.hpp>
    #include <cstdint>
    #include <cstring>
    #include <vector>
    
    using namespace cv;
    
    struct ScreenShot
    {
        ScreenShot(int x, int y, int width, int height):
            x(x),
            y(y),
            width(width),
            height(height)
        {
            display = XOpenDisplay(nullptr);
            root = DefaultRootWindow(display);
    
            init = true;
        }
    
        void operator() (Mat& cvImg)
        {
            if(init == true)
                init = false;
            else
                XDestroyImage(img);
    
            img = XGetImage(display, root, x, y, width, height, AllPlanes, ZPixmap);
    
            cvImg = Mat(height, width, CV_8UC4, img->data);
        }
    
        ~ScreenShot()
        {
            if(init == false)
                XDestroyImage(img);
    
            XCloseDisplay(display);
        }
    
        Display* display;
        Window root;
        int x,y,width,height;
        XImage* img;
    
        bool init;
    };
    
    int main(int, char**)
    {
        for(;;){
            ScreenShot screen(0,0,1366,768);
    
            Mat img;
            screen(img);
    
            imshow("img", img);
            if(waitKey(30) >= 0) break;
        }
        return 0;
    }
    

    根据@abc 发布的答案,我使用了以下代码。

    另外我是用Visual Studio Code和g++编译的:

    g ++ -std=c++0x main.cpp -lX11 'pkg-config --cflags opencv' 'pkg-config --libs opencv' -o main

    Xlib.h 和 Xutil.h (ubuntu 14.04) 的路径:/usr/include/X11

    【讨论】:

    • 必须使用以下代码编译 g++ $(pkg-config opencv4 --cflags) -std=c++0x main.cpp -lX11 `pkg-config --cflags opencv4` `pkg- config --libs opencv4` -o main
    猜你喜欢
    • 2017-02-27
    • 1970-01-01
    • 2010-11-19
    • 2012-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多