使用pybind11生成C++的Python binding示例

27 篇文章 7 订阅
15 篇文章 0 订阅

前言

C++作为一种编译型语言,在其设计之初就偏重于性能、效率和灵活性,偏向应用于系统编程、嵌入式、资源受限的软件和系统。Python作为一种解释型语言,内置了如str, tuple, list, dict等常用数据结构,支持自动垃圾回收,拥有简洁的语法、丰富的内置库和第三方库,被越来越广泛地使用在各种场景中。但Python在高便捷性的同时无可避免的缺乏高性能。

在部分应用场景中,我们需要在Python的灵活性上架构应用,底层算法希望借助C++的高性能, 那么我们可以考虑将C++开发的模块做成Python的bindings供Python调用。

在这里插入图片描述

而pybind11则是C++和Python两种语言之间的一座桥梁。Pybind11是一个轻量级只包含头文件的库,用于C++和Python之间的接口转换,可以为C++代码创建Python bindings。Pybind11通过C++编译时的自省来推断类型信息,来最大程度地减少传统拓展 Python 模块时繁杂的样板代码, 已实现STL数据结构、智能指针、类、函数重载、实例方法等到Python的映射转换,其中函数可以接收和返回自定义数据类型的值、指针或引用。

步骤

以基于OpenCV和CUDA实现的GPU版本导向滤波算法为例,演示Pybind11的应用。

代码地址:https://github.com/TracelessLe/pybind11_guidedfilter_cuda

  1. 创建工程目录pybind11_guidedfilter_cuda和C++代码
    在这里插入图片描述
    其中核心代码guidedfilter.cppguidedfilter.h来自Github用户acstacey的项目“GLFCV - Light field disparity estimation using a guided filter cost volume”。

    部分代码一瞥:
    在这里插入图片描述
    guidedfilter.cpp前面加入下述代码

    #include <pybind11/pybind11.h>
    #include "ndarray_converter.h"
    namespace py = pybind11;
    

    在末尾加上下述代码:

    PYBIND11_MODULE(gfcuda, m)
    {
        NDArrayConverter::init_numpy();
    
        m.def("guidedFilter", &guidedFilter, "cv::cuda::guidedFilter", py::arg("guide"), py::arg("src"), py::arg("radius"), py::arg("eps"), py::arg("dDepth"));
    
    }
    

    目的是通过pybind11针对此cpp生成一个名为gfcuda的python binding,通过Python下的gfcuda.guidedFilter()的方法指向的C++函数"cv::cuda::guidedFilter"

  2. 拷贝Pybind11工程到工程目录pybind11_guidedfilter_cuda
    在这里插入图片描述
    Pybind11 CSDN资源链接:https://download.csdn.net/download/TracelessLe/13204469
    Pybind11 GitHub链接:https://github.com/pybind/pybind11
    将下载下来的包进行解压,解压后的pybind11文件夹拷贝到工程目录pybind11_guidedfilter_cuda下。

  3. 拷贝ndarray_converter.cppndarray_converter.h【此项目需要】
    由于涉及到numpy数组转换,拷贝相应.cpp和.h文件。
    其中ndarray_converter.cppndarray_converter.h来自Github用户mfedoseeva的项目“roboy-activity-recognition”。

  4. 编写CMakeLists.txt
    CMakeLists.txt是C++项目的编译链接设置文件,在本项目中,需要用到的外部依赖有OpenCV和CUDA,所以需要根据你自己的路径去设置。另外需要注意添加pybind11_add_module(gfcuda ${SOURCES})等用于生成binding的语句。总体设置如下:

    #Change this if you need to target a specific CMake version
    cmake_minimum_required(VERSION 2.8)
    
    # Enable C++11
    set(CMAKE_CXX_STANDARD 11)
    
    # Set up project
    project(guided_filter_cuda)
    add_subdirectory(pybind11)
    
    
    SET(SOURCES
      ${CMAKE_CURRENT_SOURCE_DIR}/guidedfilter.cpp
      ${CMAKE_CURRENT_SOURCE_DIR}/ndarray_converter.cpp
      ${CMAKE_CURRENT_SOURCE_DIR}/ndarray_converter.h
    )
    
    pybind11_add_module(gfcuda ${SOURCES})
    
    # Detect and add OpenCV
    set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}" ${CMAKE_MODULE_PATH})
    find_package(OpenCV REQUIRED HINTS /usr/local/share/opencv4)
    
    find_package(CUDA 10.2 REQUIRED)
    
    # Define sources and executable
    set(EXECUTABLE_NAME "gfcuda")
    
    # Link against OpenCV
    include_directories(${OpenCV_INCLUDE_DIR})
    target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${OpenCV_LIBS})
    
    # Link against CUDA
    include_directories(${CUDA_INCLUDE_DIRS})
    target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${CUDA_LIBRARIES})
    

    特别注意设置OpenCV和CUDA库的路径:

    target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${OpenCV_LIBS})
    
    target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${CUDA_LIBRARIES})
    
  5. 编译

    cmake .
    make
    

    编译完成后会生成一个600K左右的.so文件如“gfcuda.cpython-37m-x86_64-linux-gnu.so”,将这个.so文件拷贝到你的工程目录即可通过import gfcuda使用。
    在这里插入图片描述

  6. 在Python中使用

    import cv2
    import gfcuda
    
    img = cv2.imread('cat.png')
    guide = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    final_img = gfcuda.guidedFilter(guide=guide, src=img, radius=5, eps=50*50, dDepth=-1)
    cv2.imwrite('out.png', final_img)
    

    cat.png
    cat.png
    out.png
    out.png

    示例代码test_gfcuda.py已经提供在工程目录下。

特别说明

① 感谢Github用户acstacey,项目“GLFCV - Light field disparity estimation using a guided filter cost volume”提供代码guidedfilter.cppguidedfilter.h

②感谢Github用户mfedoseeva,项目“roboy-activity-recognition”提供代码ndarray_converter.cppndarray_converter.h

③关于更多Pybind11对C++和Python的数据类型映射关系可以参考文末【参考资料】一栏。

在这里插入图片描述

参考资料

[1] pybind11 docs “pybind11— Seamless operability between C++11 and Python”
[2] GitHub - acstacey / GLFCV - Light field disparity estimation using a guided filter cost volume
[3] GitHub - mfedoseeva / roboy-activity-recognition
[4] GitHub - atilimcetin / Guided filter for OpenCV
[5] 知乎 - python调用c++利器–pybind11
[6] 知乎 - pybind11: C++ 工程如何提供 Python 接口
[7] 知乎 - 191123 使用 Pybind11 和 OpenCV 创建 Python 库
[8] GitHub - How OpenCV-Python Bindings Works?
[9] Docs » 1. OpenCV简介 » 1.7. OpenCV Python绑定
[10] Python Bindings: Calling C or C++ From Python
[11] GitHub - TracelessLe / pybind11_guidedfilter_cuda
[12] 知乎 - pybind11的最佳实践
[13] cnblogs - 基于pybind11实现Python调用c++编写的CV算法–下 (Linux+Cmake)

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
下面是使用 C++ 和 FFmpeg 创建 RTSP 服务器的示例代码: ```c++ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <pthread.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <libavutil/imgutils.h> #define WIDTH 640 #define HEIGHT 480 #define FPS 25 #define BITRATE 400000 AVFormatContext *fmt_ctx; AVCodecContext *codec_ctx; AVCodec *codec; AVFrame *frame; AVPacket pkt; int sockfd; struct sockaddr_in serv_addr, cli_addr; void *send_video(void *arg) { int ret; while (1) { ret = avcodec_send_frame(codec_ctx, frame); if (ret < 0) { fprintf(stderr, "Error sending frame: %s\n", av_err2str(ret)); break; } while (1) { ret = avcodec_receive_packet(codec_ctx, &pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { fprintf(stderr, "Error receiving packet: %s\n", av_err2str(ret)); break; } ret = sendto(sockfd, pkt.data, pkt.size, 0, (struct sockaddr *)&cli_addr, sizeof(cli_addr)); if (ret < 0) { fprintf(stderr, "Error sending packet: %s\n", strerror(errno)); break; } av_packet_unref(&pkt); } usleep(1000000/FPS); } return NULL; } int main(int argc, char *argv[]) { int ret; AVFrame *rgb_frame; struct hostent *he; he = gethostbyname("0.0.0.0"); if (argc < 2) { fprintf(stderr, "Usage: %s <port>\n", argv[0]); return 1; } sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { fprintf(stderr, "Error opening socket: %s\n", strerror(errno)); return 1; } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = ((struct in_addr *)he->h_addr_list[0])->s_addr; serv_addr.sin_port = htons(atoi(argv[1])); ret = bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); if (ret < 0) { fprintf(stderr, "Error binding socket: %s\n", strerror(errno)); return 1; } av_register_all(); avformat_network_init(); ret = avformat_alloc_output_context2(&fmt_ctx, NULL, "rtsp", NULL); if (ret < 0) { fprintf(stderr, "Error creating output context: %s\n", av_err2str(ret)); return 1; } codec = avcodec_find_encoder_by_name("libx264"); if (!codec) { fprintf(stderr, "Codec not found\n"); return 1; } codec_ctx = avcodec_alloc_context3(codec); if (!codec_ctx) { fprintf(stderr, "Could not allocate codec context\n"); return 1; } codec_ctx->codec_id = codec->id; codec_ctx->width = WIDTH; codec_ctx->height = HEIGHT; codec_ctx->bit_rate = BITRATE; codec_ctx->time_base = (AVRational){1, FPS}; codec_ctx->framerate = (AVRational){FPS, 1}; if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; ret = avcodec_open2(codec_ctx, codec, NULL); if (ret < 0) { fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret)); return 1; } frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Could not allocate frame\n"); return 1; } frame->format = AV_PIX_FMT_YUV420P; frame->width = WIDTH; frame->height = HEIGHT; ret = av_frame_get_buffer(frame, 32); if (ret < 0) { fprintf(stderr, "Could not allocate video data\n"); return 1; } rgb_frame = av_frame_alloc(); if (!rgb_frame) { fprintf(stderr, "Could not allocate frame\n"); return 1; } rgb_frame->format = AV_PIX_FMT_RGB24; rgb_frame->width = WIDTH; rgb_frame->height = HEIGHT; ret = av_frame_get_buffer(rgb_frame, 32); if (ret < 0) { fprintf(stderr, "Could not allocate video data\n"); return 1; } SwsContext *sws_ctx = sws_getContext(WIDTH, HEIGHT, AV_PIX_FMT_RGB24, WIDTH, HEIGHT, AV_PIX_FMT_YUV420P, 0, NULL, NULL, NULL); if (!sws_ctx) { fprintf(stderr, "Could not create sws context\n"); return 1; } pthread_t thread; ret = pthread_create(&thread, NULL, send_video, NULL); if (ret) { fprintf(stderr, "Error creating thread: %s\n", strerror(ret)); return 1; } int count = 0; while (1) { ret = av_frame_make_writable(rgb_frame); if (ret < 0) { fprintf(stderr, "Error making frame writable: %s\n", av_err2str(ret)); break; } for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { int r = (x + count) % 256; int g = (y + count) % 256; int b = (x + y + count) % 256; rgb_frame->data[0][y * rgb_frame->linesize[0] + x * 3] = r; rgb_frame->data[0][y * rgb_frame->linesize[0] + x * 3 + 1] = g; rgb_frame->data[0][y * rgb_frame->linesize[0] + x * 3 + 2] = b; } } count++; sws_scale(sws_ctx, rgb_frame->data, rgb_frame->linesize, 0, HEIGHT, frame->data, frame->linesize); frame->pts = av_rescale_q(count, codec_ctx->time_base, fmt_ctx->streams[0]->time_base); ret = av_interleaved_write_frame(fmt_ctx, &pkt); if (ret < 0) { fprintf(stderr, "Error writing video frame: %s\n", av_err2str(ret)); break; } av_packet_unref(&pkt); usleep(1000000/FPS); } av_write_trailer(fmt_ctx); close(sockfd); return 0; } ``` 这个示例代码会创建一个 RTSP 服务器,它会发送一个彩色条纹视频。要使用它,请按照以下步骤操作: 1. 编译代码。你需要安装 FFmpeg 库并使用以下命令编译代码: ``` g++ -o rtsp_server rtsp_server.cpp `pkg-config --libs --cflags libavcodec libavformat libswscale` ``` 2. 运行代码。你需要指定一个端口号作为命令行参数: ``` ./rtsp_server 8554 ``` 这会创建一个 RTSP 服务器,它会监听指定的端口。 3. 使用 VLC 播放器连接到服务器。打开 VLC 播放器,选择“Media” → “Open Network Stream”,输入 `rtsp://localhost:8554/test` 并点击“Play”。 现在,你应该能够在 VLC 播放器中看到彩色条纹视频。你可以修改代码来发送自己的视频流。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TracelessLe

❀点个赞加个关注再走吧❀

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值