0%

简介

  • mongoose的出站入站缓冲区接口
  • IO缓冲区由结构mg_iobuf描述,是一个简单的数据结构,可以在任意偏移量插入或删除数据块,并自动增长/收缩。

struct mg_iobuf

  • 简介:

    • 通用IO缓冲区。size指定buf指向的数据的分配大小,len指定当前存储的字节数。
  • 原型:

    1
    2
    3
    4
    5
    6
    struct mg_iobuf {
    unsigned char *buf; // Pointer to stored data
    size_t size; // Total size available
    size_t len; // Current number of bytes
    size_t align; // Alignment during allocation
    };

mg_iobuf_init

  • 简介:

    • 初始化IO缓冲区,分配字节大小
  • 原型:

    1
    int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align);
  • 参数:

    • io – 要初始化的mg_iobuf结构的指针
    • size – 分配的字节数
    • align – 将大小对齐到对齐内存边界。0表示不对齐
  • 返回值:

    • 1 – 成功
    • 0 – 失败
  • 示例:

    1
    2
    3
    4
    struct mg_iobuf io;
    if (mg_iobuf_init(&io, 0, 64)) {
    // io successfully initialized
    }

mg_iobuf_resize

  • 简介:

    • 调整IO缓冲区的大小,将新的大小设置为size。在此之后,io->buf指针可能会发生变化,例如缓冲区变大。如果size为0,则释放io->buf并将其设置为NULL,同时将size和len都设置为0。 得到的io->size总是与io->align字节边界对齐;因此,为了避免内存碎片和频繁的重新分配,将io->align设置为更高的值。
  • 原型:

    1
    int mg_iobuf_resize(struct mg_iobuf *io, size_t size);
  • 参数:

    • io – 需要调整大小的iobuf
    • size – 新的大小
  • 返回值:

    • 1 – 成功
    • 0 – 失败
  • 示例:

    1
    2
    3
    4
    5
    6
    struct mg_iobuf io;
    mg_iobuf_init(&io, 0, 10); // An empty buffer with 10-byte alignment

    if (mg_iobuf_resize(&io, 1)) {
    // New io size is 10
    }

mg_iobuf_free

  • 简介:

    • 释放io->buf指向的内存并设置为NULL。size和len都被设置为0。
  • 原型:

    1
    void mg_iobuf_free(struct mg_iobuf *io);
  • 参数:

    • io – 需要释放的iobuf
  • 返回值:

  • 示例:

    1
    2
    3
    4
    5
    6
    struct mg_iobuf io;
    // IO buffer initialization
    // ...

    // Time to cleanup
    mg_iobuf_free(&io);

mg_iobuf_add

  • 简介:

    • 在偏移量处插入数据缓冲区buf、len。如果需要,iobuf将被扩展。产生的io->size总是与io->align字节边界对齐;因此,为了避免内存碎片和频繁的重新分配,将align设置为一个较大的值。
  • 原型:

    1
    size_t mg_iobuf_add(struct mg_iobuf *io, size_t offset, const void *buf, size_t len);
  • 参数:

    • io – 需要追加数据的iobuf
    • offset – 追加数据的偏移
    • buf – 追加的数据
    • len – 追加数据的长度
  • 返回值:

    • 更新后iobuf的长度
  • 示例:

    1
    2
    3
    struct mg_iobuf io;         // Declare buffer
    mg_iobuf_init(&io, 0, 16); // Initialise empty buffer with 16 byte alignment
    mg_iobuf_add(&io, io.len, "hello", 5); // Append "hello"

mg_iobuf_del

  • 简介:

    • 删除从偏移量开始的len字节,并移动剩余的字节。如果len大于io->len,则不会发生任何操作,因此这种调用会被忽略。
  • 原型:

    1
    size_t mg_iobuf_del(struct mg_iobuf *io, size_t offset, size_t len);
  • 参数:

    • io – 需要删除数据的iobuf
    • offset – 开始位置的偏移量
    • len – 删除的字节数
  • 返回值:

    • 更新后iobuf的长度
  • 示例:

    1
    2
    3
    4
    struct mg_iobuf io;
    mg_iobuf_init(&io, 0, 16); // Empty buffer, 16-bytes aligned
    mg_iobuf_add(&io, 0, "hello", 2); // io->len is 5, io->size is 16
    mg_iobuf_del(&io, 1, 3); // io->len is 2, io->size is still 16

简介

  • RPC(Remote Procedure Call, 远程过程调用),是一种通信协议和编程模型,用于实现分布式系统中不同节点之间的远程调用。它允许在不同计算机或进程之间像调用本地函数一样调用远程函数,隐藏了底层网络通信的细节
  • Mongoose包含一组函数,通过RPC方法简化服务器端处理。

struct mg_rpc

  • 简介:

    • RPC方法处理程序结构。每个方法在一个链表中都有一个入口,每个入口指向一个字符串,该字符串描述了将调用该方法的模式,以及为满足该方法调用而调用的函数,并带有一个适当的函数参数。
  • 原型:

    1
    2
    3
    4
    5
    6
    struct mg_rpc {
    struct mg_rpc *next; // Next in list
    struct mg_str method; // Method pattern
    void (*fn)(struct mg_rpc_req *); // Handler function
    void *fn_data; // Handler function argument
    };

struct mg_rpc_req

  • 简介:

    • RPC请求描述符。被调用的方法会收到一个包含请求的描述符,以及一个指向函数的指针,该函数将被调用来打印输出响应,并带有一个适当的函数参数;例如:mg_pfn_realloc() 或 mg_pfn_iobuf()
  • 原型:

    1
    2
    3
    4
    5
    6
    7
    8
    struct mg_rpc_req {
    struct mg_rpc **head; // RPC handlers list head
    struct mg_rpc *rpc; // RPC handler being called
    mg_pfn_t pfn; // Response printing function
    void *pfn_data; // Response printing function data
    void *req_data; // Arbitrary request data
    struct mg_str frame; // Request, e.g. {"id":1,"method":"add","params":[1,2]}
    };

mg_rpc_add

  • 简介:

    • 将方法method_pattern添加到RPC方法列表的头部。调用该方法将调用handler,并将handler_data与请求一起传递给它(如下使用示例中的r->fn_data)。
  • 原型:

    1
    2
    void mg_rpc_add(struct mg_rpc **head, struct mg_str method_pattern,
    void (*handler)(struct mg_rpc_req *), void *handler_data);
  • 参数:

    • head – 链表指针
    • method_pattern – 方法的名称
    • handler – 执行该方法操作的RPC函数
    • handler_data – 任意函数数据
  • 注:

    • 如果method_pattern为空字符串,该处理程序将被调用来处理JSON-RPC响应。如果JSON请求是由双方发起的,那么处理响应可能是必要的。
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    struct mg_rpc *s_rpc_head = NULL;

    static void rpc_sum(struct mg_rpc_req *r) {
    double a = 0.0, b = 0.0;
    mg_json_get_num(r->frame, "$.params[0]", &a);
    mg_json_get_num(r->frame, "$.params[1]", &b);
    mg_rpc_ok(r, "%g", a + b);
    }

    static void rpc_mul(struct mg_rpc_req *r) {//...}
    }

    mg_rpc_add(&s_rpc_head, mg_str("sum"), rpc_sum, NULL);
    mg_rpc_add(&s_rpc_head, mg_str("mul"), rpc_mul, NULL);

mg_rpc_del

  • 简介:

    • 从RPC方法列表中删除带有RPC函数处理程序的方法
  • 原型:

    1
    void mg_rpc_del(struct mg_rpc **head, void (*handler)(struct mg_rpc_req *));
  • 参数:

    • head – 链表指针
    • handler – RPC函数处理程序的方法,使用NULL删除全部
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    struct mg_rpc *s_rpc_head = NULL;
    // add methods
    // ...

    // Time to cleanup
    mg_rpc_del(&s_rpc_head, rpc_mul); // Deallocate specific handler
    mg_rpc_del(&s_rpc_head, NULL); // Deallocate all RPC handlers

mg_rpc_process

  • 简介:

    • 为这个请求调用合适的方法。如果请求的方法不存在,将调用mg_rpc_err()并打印错误提示。
  • 原型:

    1
    void mg_rpc_process(struct mg_rpc_req *req);
  • 参数:

    • req – 一个请求
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    struct mg_rpc *s_rpcs = NULL;                               // Empty RPC list head
    mg_rpc_add(&s_rpcs, mg_str("rpc.list"), mg_rpc_list, NULL); // Add rpc.list
    // ... add more RPC methods

    // On request, process the incoming frame
    struct mg_str req = mg_str("{\"id\":1,\"method\":\"sum\",\"params\":[1,2]}");
    struct mg_iobuf io = {0, 0, 0, 512}; // Empty IO buf, with 512 realloc granularity
    struct mg_rpc_req r = {
    .head = &s_rpcs, // RPC list head
    .rpc = NULL, // This will be set by mg_rpc_process()
    .pfn = mg_pfn_iobuf, // Printing function: print into the io buffer
    .pfn_data = &io, // Pass our io buffer as a parameter
    .req_data = NULL, // No specific request data
    .frame = req, // Specify incoming frame
    };

    mg_rpc_process(&r);
    if (io.buf != NULL) printf("Response: %s\n", (char *) io.buf);
    mg_iobuf_free(&io);

mg_rpc_ok, mg_rpc_vok

  • 简介:

    • 打印结果帧的辅助函数
  • 原型:

    1
    2
    void mg_rpc_ok(struct mg_rpc_req *, const char *fmt, ...);
    void mg_rpc_vok(struct mg_rpc_req *, const char *fmt, va_list *ap);
  • 参数:

    • req – 一个请求
    • fmt – 使用printf语义格式化的字符串
  • 示例:

    1
    2
    3
    4
    5
    6
    static void rpc_sum(struct mg_rpc_req *r) {
    double a = 0.0, b = 0.0;
    mg_json_get_num(r->frame, "$.params[0]", &a);
    mg_json_get_num(r->frame, "$.params[1]", &b);
    mg_rpc_ok(r, "%g", a + b);
    }

mg_rpc_err, mg_rpc_verr

  • 简介:

    • 打印错误帧的辅助函数
  • 原型:

    1
    2
    void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt, ...);
    void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt, va_list *);
  • 参数:

    • req – 一个请求
    • fmt – 使用printf语义格式化的字符串
  • 示例:

    1
    2
    3
    4
    static void rpc_dosome(struct mg_rpc_req *r) {
    ...
    mg_rpc_err(r, -32109, "\"%.*s not found\"", len, &r->frame.ptr[offset]);
    }

mg_rpc_list

  • 简介:

    • 内置RPC方法,用于列出所有已注册的RPC方法。该函数通常不直接调用,而是作为方法注册。
  • 原型:

    1
    void mg_rpc_list(struct mg_rpc_req *r);
  • 参数:

    • req – 一个请求
  • 示例:

    1
    mg_rpc_add(&s_rpc_head, mg_str("rpc.list"), mg_rpc_list, &s_rpc_head);

简介

  • mongoose工具的计时器相关笔记

struct mg_timer

  • 简介:

    • 计时器结构体。描述一个软件计时器。计时器粒度与主事件循环中的mg_mgr_poll()的超时参数相同
  • 原型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct mg_timer {
    uint64_t period_ms; // Timer period in milliseconds
    uint64_t expire; // Expiration timestamp in milliseconds
    unsigned flags; // Possible flags values below
    #define MG_TIMER_ONCE 0 // Call function once
    #define MG_TIMER_REPEAT 1 // Call function periodically
    #define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set
    void (*fn)(void *); // Function to call
    void *arg; // Function argument
    struct mg_timer *next; // Linkage
    };

mg_timer_add

  • 简介:

    • 设置一个计时器。这是一个高级计时器API,允许将软件计时器添加到活动管理器。
    • 这个函数使用calloc()一个新的计时器,并将它添加到mgr->timers列表中。
    • 当调用mg_mgr_poll()时,所有添加的计时器均进行轮询,并在计时器到期时调用计时器设定的函数
  • 原型:

    1
    struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t period_ms, unsigned flags, void (*fn)(void *), void *fn_data);
  • 参数:

    • mgr – 指向事件管理器的结构体指针
    • ms – 一个时间间隔,以毫秒为单位
    • flags – 计时器标志掩码:MG_TIMER_REPEATMG_TIMER_RUN_NOW
    • fn – 函数调用
    • fn_data – 调用的函数参数
  • 返回值:

    • 返回一个指向创建的计时器的指针
  • 详解:

    • 确保计时器的间隔等于或大于mg_mgr_poll()的超时时间
  • 示例:

    1
    2
    3
    4
    5
    void timer_fn(void *data) {
    // ...
    }

    mg_timer_add(mgr, 1000, MG_TIMER_REPEAT, timer_fn, NULL);

mg_timer_init

  • 简介:

    • 设置一个计时器
  • 原型:

    1
    void mg_timer_init(struct mg_timer **head, struct mg_timer *t, uint64_t period_ms, unsigned flags, void (*fn)(void *), void *fn_data);
  • 参数:

    • head – 指向mg_timer队列头部的指针
    • t – 指向一个需要被初始化的mg_timer
    • ms – 时间间隔,以毫秒为单位
    • flags – 计时器标志掩码:MG_TIMER_REPEATMG_TIMER_RUN_NOW
    • fn – 函数调用
    • fn_data – 调用的函数参数
  • 返回值:

  • 示例

    1
    2
    3
    4
    5
    6
    void timer_fn(void *data) {
    // ...
    }

    struct mg_timer timer, *head = NULL;
    mg_timer_init(&head, &timer, 1000, MG_TIMER_REPEAT, timer_fn, NULL);

mg_timer_free

  • 简介:

    • 释放计时器,将其从内部计时器列表中删除。
  • 原型:

    1
    void mg_timer_free(struct mg_timer **head, struct mg_timer *t);
  • 参数:

    • head – 指向mg_timer队列头部的指针
    • t – 需要释放的计时器
  • 返回值:

  • 示例:

    1
    2
    3
    struct mg_timer timer;
    // ...
    mg_timer_free(&timer);

mg_timer_poll

  • 简介:

    • 如果当前的时间戳uptime_ms超过了计时器的到期时间,则计时器遍历列表,并调用它们
  • 原型:

    1
    void mg_timer_poll(struct mg_timer **head, uint64_t uptime_ms);
  • 参数:

    • head – 指向mg_timer列表头部的指针
    • uptime_ms – 当前时间戳
  • 返回值:

  • 示例:

    1
    mg_timer_poll(mg_millis());

mg_millis

  • 简介:

    • 以毫秒为单位返回当前运行时间。
  • 原型:

    1
    int64_t mg_millis(void);
  • 参数:

  • 返回值:

    • 当前时间
  • 示例:

    1
    int64_t uptime = mg_millis();

简介

  • TLS(Transport Layer Security),是一种用于保护网络通信安全性的加密协议。它建立在传输层协议(例如TCP)之上,用于在客户端和服务器之间创建安全的通信通道

struct mg_tls_opts

  • 简介:

    • TLS初始化结构
  • 原型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct mg_tls_opts {
    const char *ca; // CA certificate file. For both listeners and clients
    const char *crl; // Certificate Revocation List. For clients
    const char *cert; // Certificate
    const char *certkey; // Certificate key
    const char *ciphers; // Cipher list
    struct mg_str srvname; // If not empty, enables server name verification
    struct mg_fs *fs; // FS API for reading certificate files
    };
  • 参数:

    • ca – 证书颁发机构。可以是文件名或字符串。用于验证另一端发送给我们的证书。如果为空,则禁用证书检查。
    • crl – 证书吊销列表。可以是文件名或字符串。用于验证另一端发送给我们的证书。如果为空,则禁用证书吊销检查。
    • cert – 我们自己的证书。可以是文件名或字符串。如果为NULL,则不与对方进行认证。
    • certkey – 证书的密钥。有时,证书和它的密钥绑定在一个.pem文件中,在这种情况下,cert和certkey的值可能是相同的
    • ciphers – 允许使用的密码列表
    • srvname – 启用服务器名称验证
  • 注:

    • 如果ca和cert都设置了,那么所谓的双向TLS就启用了,这时双方都要互相验证。通常,服务器端连接同时设置ca和cert,而客户端只设置ca。

mg_tls_init

  • 简介:

    • 在给定连接上初始化TLS
  • 原型:

    1
    void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts);
  • 参数:

    • c – 需要初始化TLS的连接
    • opts – TLS初始化参数
  • 返回值:

  • 示例:

    1
    2
    struct mg_tls_opts opts = {.cert = "ca.pem"};
    mg_tls_init(c, &opts);
  • 注:

    • mbedTLS使用mg_random作为RNG。mg_random可以通过设置MG_ENABLE_CUSTOM_RANDOM=1并定义自己的mg_random()实现来覆盖。

简介

  • websocket相关函数

struct mg_ws_message

  • 简介:

    • 该结构代表Websocket消息。这个flag元素对应于RFC 6455第5.2节中所述的第一个字节。(https://www.rfc-editor.org/rfc/rfc6455#section-5.2)
  • 原型:

    1
    2
    3
    4
    struct mg_ws_message {
    struct mg_str data; // WebSocket message data
    uint8_t flags; // WebSocket message flags
    };

websocket message type

  • 简介:

    • 要从传入消息中提取消息类型,在结构体mg_ws_messageflag元素中检查四个LSBs
  • 可能存在的WebSocket消息类型:

    1
    2
    3
    4
    5
    6
    #define WEBSOCKET_OP_CONTINUE 0
    #define WEBSOCKET_OP_TEXT 1
    #define WEBSOCKET_OP_BINARY 2
    #define WEBSOCKET_OP_CLOSE 8
    #define WEBSOCKET_OP_PING 9
    #define WEBSOCKET_OP_PONG 10
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // Mongoose events handler
    void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
    if (ev == MG_EV_WS_MSG) {
    struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
    msgtype = wm->flags & 0x0F;
    if (msgtype == WEBSOCKET_OP_BINARY) {
    // This is a binary data message
    } else if (msgtype == WEBSOCKET_OP_TEXT) {
    // This is a text data message
    }
    }
    }
  • 注:

    • 当调用mg_ws_send()ms_ws_printf()发送消息时,请使用RFC 6455第5.6节中所述的正确消息类型进行数据帧(https://www.rfc-editor.org/rfc/rfc6455#section-5.6)

mg_ws_connect

  • 简介:

    • 创建客户端Websocket连接。这个函数不能连接到对端,它仅仅分配需要的资源和启动连接进程。当对端真正连接好了,会向连接事件处理函数发送一个MG_EV_CONNECT事件
  • 原型:

    1
    struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data, const char *fmt, ...);
  • 参数:

    • mgr – 使用的事件管理结构体
    • url – 指定的远程URL。例如:http://google.com
    • fn – 一个事件处理函数
    • fn_data – 一个任意指针,当事件处理函数被调用时,它会被作为fn_data传递。这个指针作为c->fn_data存储在连接结构体中
  • 返回值:

    • 返回一个指向创建的连接
    • 返回NULL表示错误
  • 示例

    1
    2
    3
    struct mg_connection *c = mg_ws_connect(&mgr, "ws://test_ws_server.com:1000",
    handler, NULL, "%s", "Sec-WebSocket-Protocol: echo\r\n");
    if(c == NULL) fatal("Cannot create connection");

mg_ws_upgrade

  • 简介:

    • 升级给定HTTP连接到Websocket。fmt是一个类似于printf()格式的字符串,用于额外的HTTP标头,返回给Websocket握手的客户端。如果不需要额外的标头,将fmt设置为空
  • 原型:

    1
    void mg_ws_upgrade(struct mg_connection *c, struct mg_http_message *, const char *fmt, ...);
  • 参数:

    • c – 使用的连接
    • hm – HTTP消息
    • fmt – 类似于printf的格式字符串,用于附加HTTP标头或为null
  • 返回值:

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    // Mongoose events handler
    void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
    if (ev == MG_EV_HTTP_MSG) {
    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
    mg_ws_upgrade(c, hm, NULL); // Upgrade HTTP to WS
    }
    }

mg_ws_send

  • 简介:

    • 向WebSocket对端发送数据
  • 原型:

    1
    size_t mg_ws_send(struct mg_connection *c, const void *buf, size_t len, int op);
  • 参数:

    • c – 使用的连接
    • buf – 需要发送的数据
    • len – 需要发送的数据大小
    • op – WebSocket消息类型,参见WebSocket message type
  • 返回值:

    • 返回发送的字节数
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    // Mongoose events handler
    void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
    if (ev == MG_EV_WS_OPEN) {
    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
    mg_ws_send(c, "opened", 6, WEBSOCKET_OP_BINARY); // Send "opened" to web socket connection
    }
    }

mg_ws_printf, mg_ws_vprintf

  • 简介:

    • ms_ws_send()相同,但是使用printf()语义格式化数据
  • 原型:

    1
    2
    size_t mg_ws_printf(struct mg_connection *, int op, const char *fmt, ...);
    size_t mg_ws_vprintf(struct mg_connection *, int op, const char *fmt, va_list *);
  • 参数:

    • c – 使用的连接
    • op – WebSocket消息类型,参见WebSocket message type
    • fmt – 使用printf()语义格式化的字符串
  • 返回值:

    • 返回发送的字节数
  • 示例:

    1
    mg_ws_printf(c, WEBSOCKET_OP_TEXT, "Hello, %s!", "world");

mg_ws_wrap

  • 简介:

    • 将输出缓冲区中的数据转换为Websocket格式。有用然后通过WebSocket实施协议。具体示例参见examples/mqtt-over-ws-client
  • 原型:

    1
    size_t mg_ws_wrap(struct mg_connection *c, size_t len, int op)
  • 参数:

    • c – 使用的连接
    • len – 转换的字节数
    • op – WebSocket消息类型
  • 返回值:

    • 返回连接结构体中输出缓冲区新的大小
  • 示例:

    1
    2
    3
    size_t len = c->send.len;         // Store output buffer len
    mg_mqtt_login(c, s_url, &opts); // Write MQTT login message
    mg_ws_wrap(c, c->send.len - len, WEBSOCKET_OP_BINARY); // Wrap it into WS

简介

  • mongoose关于URL的函数

mg_url_port

  • 简介:

    • 根据给定的URL返回端口
  • 原型:

    1
    unsigned short mg_url_port(const char *url);
  • 参数:

    • url – 需要提取端口号的URL
  • 返回值:

    • 成功,返回给定URL的端口号
    • 失败,如果URL不包含端口并且URL协议没有默认端口,则为0
  • 示例:

    1
    2
    unsigned short port1 = mg_url_port("https://myhost.com") // port1 is now 443 (default https port)
    unsigned short port2 = mg_url_port("127.0.0.1:567") // port2 is now 567

mg_url_is_ssl

  • 简介:

    • 检查给定的URL是否使用加密方案
  • 原型:

    1
    int mg_url_is_ssl(const char *url);
  • 参数:

    • url – 需要检查的URL
  • 返回值:

    • 如果URL使用了加密方案,返回0
    • 如果没有,则返回非0
  • 示例:

    1
    2
    3
    if (mg_url_is_ssl("https://example.org") == 0) {
    // scheme is encrypted
    }

mg_url_host

  • 简介:

    • 从给定的URL中提取主机名。
  • 原型:

    1
    struct mg_str mg_url_host(const char *url);
  • 参数:

    • url – 一个URL字符串
  • 返回值:

    • 主机名
  • 示例:

    1
    2
    struct mg_str a = mg_url_host("https://my.example.org:1234"); // a == "my.example.org"
    struct mg_str b = mg_url_host("tcp://[::1]"); // b == "[::1]"

mg_url_user

  • 简介:

    • 从给定的URL中提取用户名。
  • 原型:

    1
    struct mg_str mg_url_user(const char *url);
  • 参数:

    • url – 需要提取用户名的URL
  • 返回值:

    • 成功,返回用户名
    • 失败,返回空的字符串
  • 示例:

    1
    struct mg_str user_name = mg_url_user("https://user@password@my.example.org"); // user_name is now "user"

mg_url_pass

  • 简介:

    • 从给定的URL中提取密码
  • 原型:

    1
    struct mg_str mg_url_pass(const char *url);
  • 参数:

    • url – 需要提取密码的URL
  • 返回值:

    • 成功,返回密码
    • 失败,返回空的字符串
  • 示例:

    1
    struct mg_str pwd = mg_url_user("https://user@password@my.example.org"); // pwd is now "password"

mg_url_uri

  • 简介:

    • 从给定的URL中提取URI。注意,函数返回url内的指针;不需要明确地free()它。
  • 原型:

    1
    const char *mg_url_uri(const char *url);
  • 参数:

    • url – 需要提取URI的URL
  • 返回值:

    • 成功,返回URI字符串
    • 失败,返回 \
  • 示例:

    1
    const char *uri = mg_url_uri("https://example.org/subdir/subsubdir"); // `uri` is now pointer to "subdir/subsubdir"

简介

  • 本教程将向你展示如何以一系列MJPEG帧的形式发送视频流。这里使用的技术是:
    • 以不可缓存的方式提供页面,并表明内容类型为multipart/x-mixed-replace。
    • 定期发送一个不同的JPEG文件的内容,取代以前的文件

单次文件上传大小为3MB

  • 修改宏MG_MAX_RECV_SIZE
    • mongoose.h中,宏MG_MAX_RECV_SIZE默认定义为3 * 1024 * 1024
    • 修改即可

文件上传问题

HTTP协议上传文件原理

HTTP 协议中的文件上传是通过 POST 请求来实现的,具体的原理如下:

  1. 客户端发送一个 HTTP POST 请求到服务器,并在请求头中指定 Content-Type 为 multipart/form-data。这告诉服务器该请求是一个包含多部分内容的表单数据。

  2. 服务器收到请求后,解析请求头中的 Content-Type,确认请求是一个文件上传请求。

  3. 客户端将要上传的文件分割成多个部分(或者称为数据包),每个数据包都包含一个文件的一部分内容。

  4. 客户端将每个数据包作为一个独立的部分,依次发送给服务器。每个数据包都会包含一些额外的元数据,例如文件名、文件类型等。

  5. 服务器接收到每个数据包后,根据数据包的元数据信息和内容进行处理。通常情况下,服务器会将接收到的数据包保存到临时文件或内存中。

  6. 当所有数据包都接收完毕后,服务器根据接收到的数据包重新构建原始文件,并将其保存到指定的位置。

需要注意的是,文件上传的过程中可能会涉及到数据包的分片、文件大小限制、上传进度监测等细节处理。一般来说,服务器端会提供相应的接口或库来处理文件上传,以简化开发过程。

Mongoose解决方案

  • 两种情况:
    • 要上传的文件很小–明显小于可用RAM的数量。例如,我们在一个有64MB内存的嵌入式Linux系统上运行Mongoose,并上传一些大小为1KB的JSON配置。在这种情况下,我们可以使用一个标准的HTML表单上传,并在一个POST请求中接收整个文件
    • 要上传的文件很大–与可用RAM的数量相比,或者明显超过这个数量。例如,我们想上传一个大小为512KB的新文件系统镜像到ESP8266设备上,该设备有大约30KB的可用RAM。在这种情况下,没有办法在内存中保存整个文件。它应该被分成小块处理,Mongoose应该接收每个小块并将其追加到写入的文件中,直到全部接收完毕。在这里,我们可以遵循两条路径:
      • 使用单个POST请求发送文件,将文件内容作为POST主体传递–即使用二进制上传。
      • 在客户端将文件分成小块(如2Kb),并将每个小块作为一个单独的POST请求发送。

form upload

binary upload, single POST

  • 当Mongoose收到一个大的HTTP请求时,它会缓冲传入的数据,对于每一个收到的数据块,它会产生MG_EV_HTTP_CHUNK请求。当一个完整的HTTP消息被缓冲时,那么最后一个零长度的MG_EV_HTTP_CHUNK被产生,然后是MG_EV_HTTP_MSG

  • 然而,服务器端可能不会等到完整的消息被缓冲在内存中,但它可以使用mg_http_delete_chunk()函数删除传入的chunk。如果块被删除,那么在最后一个零长度的块之后就不会产生MG_EV_HTTP_MSG。这样一来,服务器就可以在块到达时进行处理–例如,将块写入文件中。

  • MG_EV_HTTP_CHUNK消息也是为表单上传生成的。然而,Mongoose并不剥离多部分标记。如果表单上传的消息被保存到一个文件中,它将包含多部分标记。

  • 0长度的chunk是最后一个chunk。使用MG_IO_SIZE构建常数来限制服务器端的最大分块大小。

  • 服务器接收每个块,处理它并删除它。当收到最后一个分块时,发送一个响应。

简介

一、RGB模型与YUV模型

  1. RGB模型
    物理三基色分别是红(Red)、绿(Green)、蓝(Blue)。现代的显示器技术就是通过组合不通强度的红绿蓝三原色,来达成几乎任何一种可见光的颜色。
    在图像存储中,通过记录每个像素的红绿蓝强度,来记录图像的方法,称为RGB模型(RGB Model)。
    常见的图片格式中,PNG和BMP这两种就是基于RGB模型的。
  2. YUV模型
    YUV模型,又被称为亮度-色度模型。它是通过数学转换,将RGB三通道转换为一个代表亮度的通道(Y),和两个代表色度的通道(UV)来记录图像的模型

二、转换过程

  1. RGB2YUV
    在做RGB信号到YUV信号的转换时,一般是先转换到YUV444格式,然后再将UV信号的分辨率降低,变成所需要的格式

  2. YUV2RGB
    在播放视频或显示图像的时候,需要将YUV信号转换成RGB信号。这个步骤称为渲染(Rendering)
    在做YUV到RGB的转换时,首先需要将缩水的UV信号的分辨率拉升到与Y信号相同的分辨率,然后再转换到RGB信号。

  3. 公式:
    Y = 0.299 * R + 0.587 * G + 0.114 * B
    U = -0.147 * R - 0.289 * G - 0.436 * B
    V = 0.615 * R - 0.515 * G - 0.100 * B

    R = Y + 1.1140 * V
    G = Y - 0.395 * U - 0.581 * V
    B = Y + 2.032 * U

收集箱:

  1. Opencv没有提供BGR转NV12,但是提供了NV12转RGB:cvtColor(src,dst,CV_YUV2BGR_NV12);
  2. 摄像机拍摄出来的视频很多都是用YUV格式保存,颜色空间的转换必须在RGB色彩模型上才能完成,所以第一步是将YUV颜色模型转换为RGB颜色模型
  3. split(image,yuvchannel);
    merge(rgbchannel,3,img2);

二值化

  1. 二值化,指将256阶的灰度图通过合适的阈值,转换为黑白二值图,即像素值只有0和1两种(或者是0和255)。
  2. 目的:通常为将图像的前后景进行分割,以便进行进一步的处理
  3. 二值化的关键在于阈值的选择,合理的阈值应该尽可能的分离前景和后景
  4. 图像二值化的算法的设计目的在于选择一个合理的阈值。在选择阈值的时候,应该是一种自适应的选择,不需要手动调节。
  5. 图像二值化阈值选择算法:
    1. P-tile算法(最古老)
    2. 最小误判概率法
    3. 大津法(OTSU)(最常用)
    4. 局部自适应二值化(Chow and Kaneko algorithm):有一些场合,单一的阈值不可能将前景和后景分开,颜色不呈现双峰性
      1. 将图像分成多个子区域,每个局部区域内的像素满足双峰性
      2. 对每个区域求解阈值
      3. 通过插值法计算每个像素的阈值

图像形态学

  1. 图像形态学的理论基础为集合论
  2. 图像中的集合代表二值图像或灰度图像的形状。如二值图像的前景像素集合
  3. 图像形态学的作用是简化图像数据,保持基本形状特性,去除不相干的结构等
  4. 基本运算包括:膨胀、腐蚀、开运算、闭运算、顶帽运算和底帽运算等。
    1. 腐蚀运算:去除一些粘连图像,去除噪声
    2. 膨胀运算:由于无法实现理想的二值化,使得原本连通的像素集合被分成不通的连通域,从而影响目标物的提取。可通过膨胀运算使其恢复连通性
    3. 膨胀和腐蚀运算的问题:使图像形状发生改变,目标物体变形,对识别时的特征提取会造成影响
    4. 开运算:先对图像进行腐蚀处理,再对结果进行膨胀处理。先腐蚀再膨胀的结果并不是恢复原状,而是会消除黏连部分,同时不影响其他部分的形状
    5. 闭运算:先对图像进行膨胀处理,再对膨胀结果进行腐蚀处理。小的裂缝,小孔等被填充,并不影响原来的形状
    6. 顶帽和底帽变换
      1. 顶帽变换:原图 - 灰度开运算结果(灰度腐蚀+灰度膨胀)
        1. 保留比结构元素小的部分
        2. 保留比周围环境亮的像素
      2. 底帽变换:灰度闭运算(灰度膨胀+灰度腐蚀) - 原图
        1. 保留比结构元素小的部分
        2. 保留比周围环境暗的像素
      3. 功能:消除背景光照不均匀的现象,从而改善在二值化时的效果,同样结构元素的尺寸大小要根据目标物体的大小进行选择

空间滤波

  1. 卷积的基本概念:
    1. 空间滤波是一种采用滤波处理的影响增强方法。其理论基础是空间卷积和空间相关。目的是改善影响质量,包括去除高频噪声与干扰,及影响边缘增强、线性增强以及去模糊等。分为低通滤波(平滑化)、高通滤波(锐化)和带通滤波。
    2. 一维卷积实例:对数字图像做卷积操作其实就是利用卷积核在图像上滑动,将图像点上的像素灰度值与对应的卷积核上的数值相乘,然后将所有相乘后的值相加作为卷积核中间像素对应的图像上像素的灰度值,并最终滑动完成所有图像的过程。
    3. 二维卷积实例:对数字图像做卷积操作其实就是利用卷积核在图像上滑动,将图像点上的像素灰度值与对应的卷积核上的数值相乘,然后将所有相乘后的值相加作为卷积核中间像素对应的图像上像素的灰度值,并最终滑动完成所有图像的过程。
  2. 卷积的应用
    1. 均值滤波
      1. 两种基本的平滑卷积:平滑卷积,通过高斯分布加权的高斯平滑卷积
      2. 效果:与周围差距较大的值趋向于与周围相似,整体值趋向于平均化
    2. 中值滤波:中值是一种非线性滤波,不需要指定卷积核,只需要指定滤波器尺寸
      1. 效果:一些和周围像素值差异特别大的点被周围的像素值代替。表现在图像就是一些特别亮或者特别暗的点被周围的像素值代替。
    3. 均值滤波和中值滤波通常的作用是:降噪,而图像中两种常见的噪声:椒盐噪声和高斯噪声
      1. 椒盐噪声:它是一种随机出现的白点或者黑点,即亮的区域有黑色像素或者在暗的区域有白色像素(或者两者皆有)。椒盐噪声的成因是图像信号受到突如其来的强烈干扰而产生的。椒盐噪声通常使用中值滤波降噪
      2. 高斯噪声:主要来源是在采集过程中产生的,例如由于照明不良或者高温引起的传感器噪声。其概率分布上符合正态分布。高斯噪声通常使用平滑滤波进行降噪。

几何变换

  1. 图像的几何变换,又称空间变换,是图形处理的一个方面,是各种图形处理算法的基础。
    1. 具体步骤:它将一幅图像中的坐标位置映射到另一幅图像中的新坐标位置,其实质是改变像素的空间位置,估算新空间位置上的像素值。
    2. 几何变换算法一般包括:空间变换运算和插值算法
  2. 二维图像的几何运算矩阵:
    1. 齐次坐标:对一个在二维平面上的点(x,y),对任意非零实数Z,三元组(xZ, yZ, Z)即称之为该店的齐次坐标。使用n+1维,来表示n维的坐标
      1. 目的:统一坐标的加法运算和乘法运算,运算时提高效率,表示无穷远的点,可以控制尺度的缩放,当z=0的时候,表示无穷远的点。
    2. 比例缩放:图像的比例缩放是指将给定的图像在x轴方向按比例缩放a倍,在y轴方向按比例缩放b倍,从而获得一幅新的图像。
      1. 如果a=b,称这样的比例缩放为图像的权比例缩放
      2. 如果a不等于b,图像的比例缩放会改变原始图像的像素间的相对位置,产生几何畸变
    3. 旋转和镜像
    4. 错切和复合变换
    5. 投影变换

视频图像处理

  1. 固定背景的视频图像处理
    1. 背景差分法:
    2. 高斯背景建模:为每一个像素计算了一个单独的阈值
    3. 阴影处理
    4. 背景更新
  2. 移动背景的视频图像处理:光流法和特定物体目标检测
    1. 光流法:
      1. 光流是空间运动物体在观测成像面上的像素运动的瞬时速度。光流场是指图像中所有像素点构成的一种二维瞬时速度场。1981年,Horn&Schunck创造性地将二维速度场和亮度变化相结合,引入基本光流约束方程及整体平滑约束条件,建立了光流计算的基本模型。
      2. 光流法的标准测试数据集:http://vision.middlebury.edu/flow/
      3. 光流法实际上是计算图像中的像素在两帧之间的运动向量

图像识别(涉及到识别,通常分为两大体系:SVM体系和神经网络体系)

  1. SVM体系:手动去设计特征,然后根据分类器进行分类
  2. 神经网络体系:通过训练自动收敛特征进行识

琐碎基础概念

边缘识别(边缘检测)

  1. 边缘识别又称边缘检测,是模仿人类视觉的一个过程。
  2. 人类视觉系统认识目标的过程分两步:首先,把图像边缘与背景分裂出来;然后,才能知觉到图像的细节,辨认出图像的轮廓。在检测物体边缘时,先对其轮廓点进行粗略检测,然后通过链接规则把原来检测到的轮廓点链接来,同时也检测和连接遗漏的边界点及去除虚假的边界。
  3. 边缘检测的目的是去发现图像中关于形状和反射或透射比的信息,是图像处理、图像分析、模式识别、计算机视觉以及人类视觉的基本步骤之一。
  4. 边缘识别的实质是采用某种算法来提取出图像中的对象和背景间的交界线。图像灰度的变化情况可以用图像灰度分布的梯度来反应,因此我们可以利用局部图像微分技术获得边缘检测算子
  5. 边缘识别步骤及要求:
    1. 图像滤波:边缘检测算法主要是基于图像亮度的一阶和二阶导数,但是导数的计算对噪声很敏感,因此必须使用滤波器来改善与噪声有关的边缘检测器的性能。(中值滤波、高斯滤波、均值滤波)
    2. 图形增强:增强边缘的基础是确定图像各点邻域强度的变化值。增强算法可以将邻域强度值有显著变化的点突出显示。
    3. 图像检测:在图像中有许多点的梯度幅值比较大,而这些点在特定的应用领域并不都是边缘,应该用某些方法来确定哪些是边缘点。最简单的边缘检测判据是梯度幅值阈值判据。
    4. 图像定位:如果某一应用场合要求确定边缘位置,则边缘的位置可以在子像素分辨率上来估计,边缘的方位也可以被估计出来。
  6. 传统边缘识别方法:
    1. 基于灰度直方图
    2. 基于梯度:梯度对应一阶导数,梯度算子就是一阶导数算子。在边缘灰度值过渡比较尖锐,且在图像噪声比较小时,梯度算子工作的效果较好,而且对施加的运算方向不予考虑,在实际中常用小区域模板进行卷积来近似计算。根据模板的大小和元素值的不用,已经提出许多不同的算子。常见的有:Roberts边缘检测算子、Sobel边缘检测算子、Prewitt边缘检测算子、Robinson边缘检测算子、Laplacan边缘检测算子、Canny边缘检测算子、LOG滤波器(Marr-Hildreth算子)等。
      1. Canny边缘检测算子:基本思想是先将图像使用高斯函数Gauss进行平滑,再由一阶微分的极大值确定边缘点。二阶微分的零交叉点不仅对应着一阶导数的极大值,而且也对应着一阶导数的极小值。换句话说,图像中灰度变化剧烈的点与变化缓慢的点都对应着二阶导数零交叉点。因此,Canny算子可能会引入伪边缘点。

OpenCV中的轮廓

  1. 什么是轮廓?
    1. 轮廓可以简单地解释为:连接具有相同颜色或强度的所有连续点(沿边界)的曲线。轮廓是用于形状分析以及对象检测和识别的有用工具。
    2. 为了获得更高的准确性,请使用二进制图像。因此,在找到轮廓之前,请应用阈值或者Canny边缘检测
    3. 在OpenCV中,找到轮廓就像从黑色背景中找到白色物体,因此请记住:要找到的对象应该是白色,背景应该是黑色。
  2. 轮廓特征
    1. 矩:图像矩可以帮助计算某些特征,例如物体的重心,物体的面积
    2. 轮廓面积:轮廓区域由函数contourArea()给出
    3. 轮廓周长(弧长):可以使用arcLength()函数找到
    4. 轮廓近似:根据指定的精度,它可以将轮廓形状近似为顶点数量较少的其他形状。它是Douglas-Peucker算法的实现
    5. 凸包:凸包外观看起来与轮廓相似,甚至在某些情况下两者提供相同的结果。在这里,使用convexHull()函数检查曲线是否存在凹凸缺陷并对其进行矫正。一般而言,凸曲线是始终凸出或至少平坦的曲线,如果在内部凸出,则称为凸度缺陷
    6. 边界矩形:
      1. 直角矩形:它是一个直角矩形,不考虑对象的旋转。因此,边界矩形的面积将不会最小,可以通过boundingRect()找到
      2. 旋转矩形:在这里,边界矩形是用最小面积绘制的,因此它也考虑了旋转的因素。使用函数minAreaRect()得到,它返回一个Box2D结构,其中包含:中心(x,y),(宽度,高度),旋转角度)。但是要绘制此矩形,需要矩形的四个角,通过函数boxPoints()获得
    7. 最小外圆:使用函数minEnclosingCircle()找到对象的外接圆,它是一个以最小面积完全覆盖对象的圆圈。
    8. 拟合椭圆:使用函数fitEllipse(),ellipse()函数
  3. 轮廓属性:
    1. 长宽比:它是对象边界矩形的宽度与高度的比率
    2. 范围:它是轮廓区域与边界矩形区域的比率
    3. 固实性:它是轮廓面积与其凸包面积的比率
    4. 等效直径:它是面积与轮廓面积相同的圆的直径
    5. 方向:它是物体指向的角度
    6. 遮罩和像素点
    7. 最大值、最小值及其位置:
    8. 平均颜色或平均强度:
    9. 极端点:它是指对象的最顶部,最底部,最右侧和最左侧的点
  4. 轮廓:更多功能:
    1. 凸包缺陷:物体与该船体的任何偏离都可以视为凸包缺陷,使用convexityDefect()来查找
    2. 点多边形测试:此功能查找图像中的点与轮廓之间的最短距离
    3. 匹配形状:matchShapes()函数能够比较两个形状或两个轮廓,并返回显示相似度的度量。结果月底,匹配越好。
  5. 轮廓层次:
    1. 什么是轮廓层次:
      1. 通常使用findContours()函数来检测图像中的对象,有时对象位于不通的位置。但是在某些情况下,某些形状位于其他形状内,就像嵌套的数字一样。在这种情况下,我们将外部的一个称为父级,将内部的一个称为子级。这样,图像中的轮廓彼此之间就具有某种关系。并且我们可以指定一个轮廓如何相互连接,例如:是其他轮廓的子轮廓,还是父轮廓等。这种关系的表示称为层次结构。
  6. OpenCV中的函数:
    1. void findContours(InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset = Point())
      1. 轮廓检测,从二进制图片中找到轮廓。
      2. 参数
        1. image:源图片
        2. contours:检测到的轮廓,每个轮廓都存储为点向量。
        3. hierarchy:可选的输出向量,包含有关图像拓扑的信息。指定子轮廓和父轮廓等。
        4. mode:轮廓检测模式
          1. RETR_EXTERNAL :仅检索极端外部轮廓
          2. RETR_LIST :检索所有轮廓而不建立任何层次关系
          3. RETR_CCOMP :检索所有轮廓并将他们组织成两级层次结构
          4. RETR_TREE :检索所有轮廓并重建嵌套轮廓的完整层次
        5. method:轮廓近似法
          1. CHAIN_APPROX_NONE :绝对存储所有轮廓点
          2. CHAIN_APPROX_SIMPLE :压缩水平、垂直和对角线段,只留下它们的端点
          3. CHAIN_APPROX_TC89_L1 :应用Teh-Chin 炼近似算法的一种风格
          4. CHAIN_APPROX_TC89_KCOS :应用Teh-Chin 炼近似算法的一种风格
        6. offset:每个轮廓点移动的可选偏移量。
    2. void approxPloyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed);
      1. 用另一个具有较少顶点的曲线/多边形来逼近一条曲线/多边形,使它们之间的距离小于或等于指定的精度。
      2. 参数
        1. curve:存储在vector或Mat中的2D点的输入向量
        2. approxCurve:近似的结果。该类型与输入曲线的类型相匹配
        3. epsilon:指定近似精度的参数。这是原始曲线与其近似值之间的最大距离
        4. closed:如果为真,则近似曲线是闭合的,否则不是闭合的
    3. Rect boundingRect(InputArray array);
      1. 计算点集或灰度图像的非零像素的右上边界矩形。该函数计算并返回灰度图像的指定点集或非零像素的最小上边界矩形
      2. 参数:array,输入灰度图像或二维点集,存储在vector或Mat中
    4. void rectangle(InputOutputArray img, Point pt1, Point pt2, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);
      1. 绘制一个简单的、填充的直角矩形
      2. 参数
        1. img:图像
        2. pt1:矩形的顶点
        3. pt2:与pt1相对的矩形的顶点
        4. color:矩形颜色或亮度(灰度图像)
        5. thickness:构成矩形的线条粗细
        6. lineType:线的类型(线路连通性)
          1. LINE_4:4连线
          2. LINE_8:8连线
          3. LINE_AA :抗锯齿线
        7. shift:坐标中的小数位数
    5. void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourldx, const Scalar& color, int thickness = 1, int lineType = LINE_8, InputArray = hierarchy = noArray(), int maxLevel = INT_MAX, Point offset = Point());
      1. 绘制轮廓或填充轮廓。如果thickness>=0,该函数在图像中绘制轮廓,如果thickness < 0,则填充轮廓所包围的区域。
      2. 参数:
        1. image:目标图像
        2. contours:所有输入轮廓。每个轮廓都存储为一个点向量
        3. contourldx:指定要绘制的轮廓的参数。如果为负数,则绘制所有轮廓。
        4. color:轮廓的颜色
        5. thickness:轮廓线的粗细
        6. lineType:线路连通性
          1. LINE_4:4连线
          2. LINE_8:8连线
          3. LINE_AA :抗锯齿线
        7. hierarchy:有关层次结构的可选信息
        8. maxLevel:绘制轮廓的最大级别。
          1. 如果为0,则仅绘制指定的轮廓。
          2. 如果为1,则函数绘制和所有嵌套轮廓
          3. 如果为2,则函数绘制轮廓、所有嵌套轮廓、所有嵌套到嵌套的轮廓,以此类推。
          4. 仅当存在可用层次结构的时候才考虑此参数
        9. offset:可选的轮廓移动参数。将所有绘制的轮廓移动指定的offset = (dx, dy)。

运动目标检测:背景差分法(Background subtraction)

  1. 背景差分法,又称背景减法,常用于检测视频图像中的运动目标,是目前目标检测的主流方法之一。这种方法是通过把当前帧(current frame)的每一个像素与背景模板(background model)的每一个像素做减法,来判断这个像素是属于前景还是背景。
  2. 基本原理:将图像序列中的当前帧和已经确定好或实时获取的背景参考模型(背景图像)做减法,找不同,计算出与背景图像像素差异超过一定阈值的区域作为运动区域,从而来确定运动物体位置、轮廓、大小等特征,非常适用于摄像机静止的场景。
  3. 背景:
    1. 背景差分法的性能好坏很大程度上取决于背景模型的建模、获取和更新方法,背景图像的建模和模拟的准确程度,将直接影响到检测的效果。
    2. 什么是背景?
      1. 对于一个稳定的监控场景而言,在没有运动目标,光照没有变化的情况下,视频图像中各个像素点的灰度值是符合随机概率分布的。由于摄像机在采集图像的过程中,会不可避免地引入噪声,这些灰度值以某一个均值为基准线,在附近做一定范围内的随机震荡,这种场景就是所谓的背景。
  4. 传统的背景建模方法有:中值法背景建模、均值法背景建模、单高斯分布模型、混合高斯分布模型、卡尔曼滤波器模型以及高级背景模型等,这些方法都是基于像素的亮度值进行数学计算处理,所以我们说运动目标检测是基于统计学原理。
    1. 中值法背景建模:在一段时间内,取连续N帧图像序列,把这N帧图像序列中对应位置的像素点灰度值按从小到大的顺序排列,然后取中间值作为背景图像中对应像素点的灰度值;
    2. 均值法背景建模:在视频图像中取连续N帧,计算这N帧图像像素灰度值的平均值来作为背景图像的像素灰度值
    3. 卡尔曼滤波器模型:该算法把背景认为是一种稳态的系统,把前景图像认为是一种噪声,用基于Kalman滤波理论的时域递归低通滤波来预测变化缓慢的背景图像,这样既可以不断地用前景图更新背景,又可以维持背景的稳定性消除噪声的干扰;
    4. 单高斯分布模型:其基本思想是,将图像中每一个像素点的灰度值看成是一个随机过程X,并假设该点的某一像素灰度值出现的概率服从高斯分布,
    5. 多高斯分布模型:将背景图像的每一个像素点按多个高斯分布的叠加来建模,每种高斯分布可以表示一种背景场景,这样的话,多个高斯模型混合使用就可以模拟出复杂场景中的多模态情形
    6. 高级背景模型:得到每个像素或一组像素的时间序列模型。这种模型能很好的处理时间起伏,缺点是需要消耗大量的内存。
  5. 背景差分法计算十分简单,此外该方法还在一定程度上克服了环境光线的影响。其缺点是不能用于运动的摄像头,同时背景图像的实时更新也并非易事。
  6. 背景差分法实现目标检测的四个环节:背景建模、背景更新、目标检测、后期处理。在其中,背景建模和背景更新是背景差分法中的核心问题。

高斯混合模型

  1. GMM,高斯混合模型,也可以简写为MOG。高斯模型就是用高斯概率密度函数(正态分布曲线)精确地量化事物,将一个事物分解为若干的基于高斯概率密度函数(正态分布曲线)形成的模型。
  2. GMMs已经在数值逼近、语音识别、图像分类、图像去噪、图像重构、故障诊断、视频分析、邮件过滤、密度估计、目标识别与跟踪领域取得了良好的效果。
  3. 对图像背景建立高斯模型的原理及过程:
    1. 图像灰度直方图反映的是图像中某个灰度值出现的频次,也可以认为是图像灰度概率密度的估计。如果图像所包含的目标区域和背景区域相比比较大,且背景区域和目标区域在灰度上有一定的差异,那么该图像的灰度直方图呈现双峰-谷形状,其中一个峰对应于目标,另一个峰对应于背景的中心灰度。对于复杂的图像,尤其是医学图像,一般是多峰的。通过将直方图的多峰特性看作是多个高斯分布的叠加,可以解决图像的分割问题。在智能监控系统中,对于运动目标的检测是中心内容,而在运动目标检测提取中,背景目标对于目标的识别和跟踪至关重要,而建模正是背景目标提取的一个重要环节。
    2. 混合高斯模型使用K(基本为3到5个)个高斯模型来表征图像中各个像素的特征,在新一帧图像获得后更新混合高斯模型,用当前图像中的每个像素点与混合高斯模型匹配,如果成功则判定该点为背景点,否则为前景点。
    3. 通观整个高斯模型,主要是由方差和均值两个参数决定,对均值和方差的学习,采用不同的学习机制,将直接影响到模型的稳定性、精确性和收敛性。
    4. 由于是对运动目标的背景提取建模,因此需要对高斯模型中方差和均值两个参数实时更新。为提取模型的学习能力,改进方法对均值和方差的更新采用不同的学习率;为提高在繁忙的场景下,大而慢的运动目标的检测效果,引入权值均值的概念,建立背景图像并实时更新,然后结合权值、权值均值和背景图像对像素点进行前景和背景的分类
    5. 主要步骤:
      1. 为图像的每个像素点指定一个初试的均值、标准差及权重。
      2. 收集N(一般取200以上,否则很难得到像样的结果)帧图像利用在线EM算法得到每个像素点的均值、标准差以及权重
      3. 从N+1帧开始检测,检测的方法,对每个像素点:
        1. 将所有的高斯核按照ω / σ 降序排序
        2. 选择满足公式的前M个高斯核:M = arg min(ω / σ > T)
        3. 如果当前像素点的像素值其中有一个满足:就可以认为其为背景点
        4. 更新背景图像,用在线EM算法
          1. EM算法(Expectation Maximization)算法是由Dsmpser、Laind和Rubin在1977年提出的一种求参数的极大似然估计方法,可以广泛地应用于处理缺损数据、截尾数据等带有噪声的不完整数据。
  4. 背景与前景:
    1. 前景是指在假设背景为静止的情况下,任何有意义的运动物体即为前景。建模的基本思想是从当前帧中提取前景,其目的是使背景更加接近当前视频帧的背景。即利用当前帧和视频序列中的当前背景帧进行加权平均来更新背景,但是由于光照突变以及其他外界环境的影响,一般的建模后的背景并非十分干净清晰,而高斯混合模型是建模最为成功的方法之一。

计算机视觉领域:目标分割、目标识别、目标检测和目标跟踪

目标分割

  1. 目标分割是要把目标对应的部分分割出来,分出前景和背景,并将背景去除。将图像输入模型以后,模型对图像进行逐帧预测,目标涉及的每个像素都标注出来。目标分割一般要求的精度较高。
  2. 它本质上也是一个分类任务,但是与图像识别不同,它不是对整个图像进行分类,而是对图像中的逐像素进行分类,例如日常在辅助驾驶中看到的车道线标注,这就是其中之一的应用

目标识别

  1. 目标识别就是将目标的类型进行分类,针对整个图像进行分类,一般基于深度学习方法。
  2. 把图像输入到模型中,模型会识别出图像中的目标是什么。这本质上是一个分类任务,即把图像中的目标进行分类,最终输出它是属于哪个类别的目标。
  3. 当我们想知道图像中具体在哪里时,仅能完成分类任务的图像识别就不能胜任了;同时,如果我们想知道图像中具体包括多少数量的同类目标时,分类任务也无法给出合理的答案

目标检测

  1. 目标检测就是检测图片中目标的具体位置和尺寸,也就是目标定位在图像分类的基础上,进一步判断目标具体在图像中的位置,一般是以bounding box的形式出现的。
  2. 把图像输入到模型中,模型需要给出回归的位置标注,除此之外,还需要给出标注位置内的目标是什么。它同事兼顾了目标的位置标注任务,以及目标的识别任务

目标跟踪

  1. 目标追踪,也成为目标跟踪,基于目标定位实时追踪目标所在的位置,主要用于视频中,利用图像帧之间的时序关系。
  2. 目标被识别以后,算法需要在接下来的时序数据中快速高效的对目标进行再定位。区别类似的目标可以避免不必要的重复计算,利用时序相关性,对一些简单的旋转、遮盖、缩小、放大等线性或非线性变化具有鲁棒性。也就是对视频中的ROI连续检测,输出每一帧图像的bounding box
  3. 目标跟踪一般建立在目标检测和目标分割之上,在连续采样的视频或图像中,当我们需要知道同一个目标在相邻视频帧数或图像中的位置识别时,目标跟踪可以很好的完成这个任务。

综上所述,目标识别(图像识别)、目标检测、目标分割以及目标跟踪,它们有着一定的联系,大部分的任务都是建立在目标识别的基础上,而目标跟踪又是在目标检测和目标分割的扩展应用。

计算机视觉(大黑书)

第一章

  1. 计算机视觉的研究目标是,根据感测到的图像对实际物体和场景做出有意义的判定。为了对实际物体做出判定,总是需要根据图像来构造它的某个描述或模型,因此专家们会说计算机视觉的目标是根据图像来构造出对场景的描述。
  2. 数字图像包含固定的像素(pixel)行数与列数,像素是图像元素(picture element)的缩写。
  3. 多幅图像运算:
    1. 两幅图像相加或者相减可以得到一副新图像。一般用图像减法检测图像随时间的变化。
  4. 图像类型:
    1. 在图像计算中,要了解模拟图像(analog image)和数字图像(digital image)两个概念。
      1. 模拟图像是指二维图像F(x, y),其空间参数x和y具有无限精度,在每个空间点(x,y)的光强也具有无限精度
      2. 数字图像是指二维图像I(r,c),用离散的二维光强阵列表示,光强的精度是有限的。

mooc

数字图像处理:
数字图像,是由模拟图像数字化得到的,以像素为基本元素的图像
数字图像处理(Digital Image Processing)又称为计算机图像处理,它是指用数字计算机或数字电路对数字图像进行处理的。

几个相关领域:
数字图像处理:图像增强、图像复原、图像压缩、图像变换、图像描述等。
计算机视觉:工业检测、图像识别、图像检索、图像理解等
计算机图形学:仿真、工业设计、游戏、电影特效
人工智能:对信息分析、控制、决策

学习内容和学习建议:
设计的专业知识
高等数学、概率论、线性代数、矩阵理论、数字信号处理、最优化理论、运筹学、图论、几何学、机器学习
数字图像处理设计的编程知识:
C++ Python matlab
常用数字图像处理开发库:
Opencv

参考书目:
《数字图像处理》 Rafael,C,Gonzalez
《OpenCV3编程入门》毛星云


Opencv简介:
OpenCV,1999年由Intel建立,开源的跨平台计算机视觉库,
主页:https://opencv.org/

编译原理简介:
C/C++代码编译过程:
C语言的编译链接过程要把编写的C程序(源代码)转换成可以在硬件上运行的程序(可执行代码)
分为两个部分:编译和链接
编译,就是把文本形式源代码翻译为机器语言形式的目标文件的过程
编译过程又分为两个阶段:编译和汇编
在编译阶段,编译器会首先进行预处理,预处理主要对下面的内容进行处理:1)宏定义指令,2)条件编译指令,3)头文件包含指令,4)特殊符号。编译阶段,编译器还会进行一些优化,检查语法规则,然后会把代码生成为汇编码。
汇编阶段会把汇编码生成为目标机器所能够读取的机器码。
链接,就是把目标文件、操作系统的启动代码和用到的库文件进行组织,形成最终生成可执行代码的流程
编译阶段生成的机器码实际上还不能马上运行。因为opencv头文件中只有函数的定义,没有函数的内容。函数的内容通常是封装在dll动态库(Linux下为.so后缀文件)或者lib静态库(Linux下为.a后缀文件)文件中,那么在链接阶段,就需要告诉编译器,去哪里找这些库文件,以及库文件的名称。

编译安装Opencv:

  • 安装配置环境
    • 安装cmake
    • 安装环境依赖build-essential libgtk2.0-dev libavcodec-dev libavformat-dev libjpeg-dev libswscale-dev libtiff5-dev libgtk2.0-dev pkg-config
  • 安装Opencv:
    • 下载压缩文件
    • 在opencv目录下: mkdir build && cd build
    • 编译Opencv: cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local/ .. && make -j8 && make install
  • 配置环境:
    • /etc/ld.so.conf 文件添加 /usr/local/lib
    • /etc/bash.bashrc 文件添加 PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig && export PKG_CONFIG_PATH
    • source /etc/bash.bashrc
  • 检验:
    • pkg-config opencv --modversion

Opencv的基本数据格式:
OpenCV早期版本中,采用IplImage格式来保存图像,到2.0以后的版本,引入面向对象的思想,采用C++重写了大量代码,并引入Mat类作为图像容器。Mat(matrix)类本身是一个矩阵格式,也可以用来保存图像

Mat的基本操作:
1.)创建:opencv提供了很多方法创建mat
1.1)使用Mat()构造函数
cv::Mat M1(2,2,CV_8UC3,Scalar(0,0,255))
这个函数的意思是,创建了一个名为M1的Mat,改Mat的尺寸为2,2,类型为CV_8UC3,即8位uchar类型,C3是指该Mat通道数为3.这个Mat的每一个元素包含了3个通道或者说3个数值,然后用0,0,255为每一个元素赋值。
这里8位uchar类型的取值为0~255,实际上如果一个Mat是用来表示RGB图像的时候应该声明为CV_8UC3型。
Mat型可以定义各种类型,定义的方式:CV_(位数)+(数据类型)+(通道数)
1.2)使用create()函数
cv::Mat M3;
M3.create(3,4,CV_8UC3);
表示首先声明一个mat型,名为M3,其尺寸为3行,4列。

2.)复制
2.1)浅复制
cv::Mat srcM(2,2,CV8UC3,Scalar(0,0,255));
cv::Mat dstM;
dstM = srcM;
表示首先声明一个mat名为srcM,并初始化,然后声明一个mat名为dstM,通过 = 把srcM复制给dstM
这样生成的矩阵,只是新生成一个矩阵头,dstM的data依然指向矩阵srcM的data,类似C++中的浅拷贝
2.2)深复制
cv::Mat srcM(2,2,CV_8UC3,Scalar(0,0,255));
cv::Mat dstM;
srcM.copyTo(dstM);
通过copyTo函数,可以实现深复制,也就是dstM是一个全新的矩阵,他在内存中的地址和srcM是不一样的,另外copyTo函数还可以加上掩模参数。

3.)遍历Mat:当我们想读取或者修改Mat的任意内容的时候,可以用以下方式访问Mat
3.1)利用指针.ptr


数字图像的基本概念
1.)数字图像的硬件介绍
1.1)图像输入设备:输入,采样量化,专用处理。(相机、摄像机、扫描仪等)
1.1.1)线阵相机和面阵相机
面阵相机:一次拍摄一个区域,视觉检测中绝大部分应用面阵相机
线阵相机:一次拍摄一行像素,通过移动以及拼接来获取图像,分辨率更高,成像质量更高,价格更贵
1.2)电脑:数字图像处理(PC机,服务器集群,硬件电路等)
1.3)图像输出设备:专用处理,D/A转换,输出。(打印机、显示器等)

2.)数字图像的几个基本概念
2.1)图像的采样和量化
数字化坐标值称为采样
数字化幅度值称为量化
2.2)图像的分辨率
采样的程度通常用采样率来表示,也就是通常所说的分辨率。分辨率160 × 128 的意思是水平像素数为160个,垂直像素数128个。分辨率越高,像素的数目越多,感受的图像越精密
2.3)图像的灰度级
最常见的图像为8位图像,灰度级为256级,即2的8次方。
灰度级越多,可以展现的图像细节就越多,有时候也把灰度级称为灰度分辨率。
2.4)图像的坐标系
在图像中,如果要表示图像中的某一个像素,可以用它的坐标来表示
图像原点为图像的左上角,坐标记做[0,0]
一副M × N的图像可以用一个矩阵来表示
2.5)像素的空间关系:8-邻接和4-邻接
3.)数字图像的种类和色彩模型
3.1)图像的种类
3.1.1)二值图像:
像素取值仅为0和1,“0“代表黑色,”1“代表白色。通常用来表示状态,如区分图像中的前景和背景
3.1.2)灰度图像:
像素取值范围为[0,255],”0”表示纯黑,“255”表示纯白色,一些图像算法中需要使用灰度图进行运算
3.1.3)彩色图像:
显示设备通常使用RGB格式的彩色图像,即红(red)绿(green)蓝(blue)三种颜色的组合叠加起来获得各种颜色。
如果把RGB值看做是3个维度的坐标,构建的空间称为RGB色彩空间
除了RGB外,常见的色彩模型还有HSV/HSI(数字图像算法常用),CMYK(主要用于印刷),YUV(用于图像传输)
3.2)色彩模型:通过数学模型表示颜色,所用的数学模型即颜色模型。
3.2.1)CMYK色彩模型
印刷业通过青(C)、品(M)、黄(Y)三原色油墨的不同网点面积率的叠印来表现颜色,一般采用青(C)、品(M)、黄(Y)、黑(BK)四色印刷。
CMYK可以看做是从黑色中减少颜色得到新的颜色,故可以称之为减色模型。而RGB是在白色上叠加颜色得到新的颜色,故称为加色模型。
3.2.2)HSV色彩模型
HSV即色相(Hue)、饱和度(Saturation)、明度(Value),又称HSI(I即intensity)。常用于图像算法中的色彩分析,对光照具有较强的鲁棒性。
H:用角度表示,从红色开始按逆时针方向计算,红色为0度,绿色为120度,蓝色为240度,该值表示颜色接近于哪种纯色值
S:通常取值范围为0%~100%。圆锥的中心为0,该值越大表示颜色越饱满,直观的说即颜色深而艳
V:亮度,表示颜色的明亮程度
4.)图像直方图
4.1)图像的直方图
直方图(histogram)是图像处理中的一个非常重要工具,被广泛应用。直方图本质是概率分布的图形化,同时直方图也可以用来表示向量。
4.1.1)直方图的作用
图像匹配:比较两幅图像的直方图,可以得到两幅图像的相似程度,其本质是对比灰度出现的概率是否相似
判断成像质量
二值化阈值:所谓二值化即通过设置一个门限值,把灰度图像转换为二值化图像,通常的目的是分离前景和背景。

简介

  • mongoose 工具

mg_call

  • 简介:

    • 发送ev事件到c事件处理程序。该函数在实现您自己的协议时非常有用。
  • 原型:

    1
    void mg_call(struct mg_connection *c, int ev, void *ev_data);
  • 参数:

    • c – 发送事件的连接
    • ev – 发送的事件
    • ev_data – 附加事件参数
  • 返回值:

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    // In a timer callback, send MG_EV_USER event to all connections
    static void timer_fn(void *arg) {
    struct mg_mgr *mgr = (struct mg_mgr *) arg;
    for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
    mg_call(c, MG_EV_USER, "hi!");
    }
    }

mg_error

  • 简介:

    • 向连接事件处理程序发送MG_EV_ERROR,并使用printf语义格式化错误信息。
  • 原型:

    1
    void mg_error(struct mg_connection *c, const char *fmt, ...);
  • 参数:

    • c – 发送事件的连接
    • fmt – 使用printf语义格式化的字符串
  • 返回值:

  • 示例:

    1
    mg_error(c, "Operation failed, error code: %d", errno);

简介

GOP, Group of picture

  • 关键帧的周期,也就是两个IDR帧之间的距离,一个帧组的最大帧数,一般而言,每一秒视频至少需要使用一个关键帧,增加关键帧个数可以改善质量,但是同时增加带宽和网络负载。
  • 需要说明的是,通过提高GOP值来提高图像质量是有限度的,在遇到场景切换的情况是,H.264解码器会自动强制插入一个帧,此时实际的GOP值被缩短了。另一个方面,在一个GOP中,P,B帧是由I帧预测得到的。当I帧的图像质量比较差时,会影响到一个GOP中后续的P,B帧的图像质量,直到下一个GOP开始才有可能恢复,所以GOP也不宜设置过大。

码率/码流

  • 码流(Data Rate)是指视频文件在单位时间内使用的数据流量,也叫码率或码流率,通俗一点的理解就是取样率,是视频编码中画面质量控制中最重要的部分,一般我们用的单位是kb/s或者Mv/s。
  • 一般来说同样分辨率下,视频文件的码流越大,压缩比就越小,画面质量就越高。
    • 码流越大,说明单位时间内取样率越大,数据流,精度就越高,处理出来的文件就越接近原始文件,图像质量越好,画质越清晰,要求播放设备的解码能力也越高。
  • 当然,码流越大,文件体积也越大,其计算公式是:文件体积=时间*码率/8
  • 通常来说,一个视频文件包括了画面及声音,例如一个RMVB的视频文件,里面包含了视频信息和音频信息,音频及视频都有各自不同的采样方式和比特率,也就是说,同一个视频文件,音频和视频的比特率并不是一样的,而我们所说的一个视频文件码流率大小,一般是指视频文件中音频及视频信息码流率的综合。
  • 以国内最流行的RMVB视频文件为例,RMVB中的VB,指的是VBR,即Variable Bit Rate的缩写,中文含义是可变比特率,他标识RMVB采用的是动态编码的方式,把较高的采样率用于复杂的动态画面,而把较低的采样率用于静态画面,合理利用资源,达到画质与体积可兼得的效率
  • 码率和取样率最根本的差别就是码率是针对源文件来讲的

采样率

  • 采样率,也成为采样速度或采样频率,定义了每秒从连续信号中提取并组成离散信号的采样个数,它用赫兹Hz来表示。
  • 采样率是指将模拟信号转换成数字信号时采样频率,也就是单位时间内采样多少点。一个采样点数据有多少比特。
  • 比特率是指每秒传送的比特数,单位为bps(bit per second),比特率越高,传送的数据越大,音质越好。
  • 比特率=采样率 * 采样位数*声道数

比特率

  • 比特率是指每秒传送的比特数,单位为bps (bit per second)
  • 比特率越高,传送的数据越大。在视频领域,比特率通常翻译为码率
  • 比特率表示经过编码(压缩)后的音,视频数据每秒钟需要用多少个比特来表示,而比特就是二进制里面最小的单位,要么是0,要么是1.
  • 比特率与音,视频压缩的关系,简单的说就是比特率越高,音,视频的质量就越好,但是编码后的文件就越大
  • 比特率是指将数字声音,视频由模拟格式转化成数字格式的采样率,采样率越高,还原后的音质,画质就越好
  • 常见编码模式:
    • VBR(Variable Bitrate), 动态比特率,也就是没有固定的比特率,压缩软件在压缩时根据音频数据即时确定使用什么比特率,这是以质量为前提兼顾文件大小的方式,推荐的编码模式
    • ABR(Average Bitrate), 平均比特率,是VBR的一种插值参数。LAME针对CBR不佳的文件体积比和VBR生成文件大小补丁的特点独创了这种编码模式。ABR在指定的文件大小内,以每50帧(30帧约一秒)为一段,低频和不敏感频率使用相对低的流量,高频和大动态表现时使用高流量,可以作为VBR和CBR的一种折中选择
    • CBR(Constant Bitrate),常数比特率,指文件从头到尾都是一种位比特率,相对于VBR和ABR来说,它压缩出来的文件体积最大,而音质相对于它们来说不会有明显的提高

帧速率

  • 帧速率,也成为FPS(frames per second), 是指每秒刷新的图片的帧数,也可以理解为图形处理器每秒钟能够刷新几次

分辨率

  • 就是帧大小,每一帧就是一幅图像
  • 计算输出文件大小公式:
    • (音频编码率(KBit为单位)/8 + 视频编码率(KBit为单位)/8) * 影片总长度(秒为单位) = 文件大小(MV为单位)

图像处理 行宽(linesize) 步长(stride) 间距(pitch)

  • 间距,有很多别名,在使用ffmpeg解码时,称为linesize;使用ffmpeg转换格式时,称为stride

  • 间距,在大多数情况下,其数值和图像的宽度是相同的

  • 现在计算机的cpu都是32位或者64位,一次最少读取4个字节或者8个字节,如果少于这些,反而要做一些额外的工作,会花更长的时间,所以会有一个概念叫做内存对齐,将结构体的长度设为4或8的倍数。

  • 间距,也是因为同样的理由出现。图像的操作通常按行操作的,如果图像的所有数据都紧密排列,那么会发生非常多次的读取非对齐内存,会影响效率

  • 间距,就是指图像中的一行图像数据所占的存储空间的长度,它是一个大于等于图像宽度的内存对齐的长度。这样每次以行为基准读取数据的时后就能内存对齐,虽然可能会导致内存浪费,但是在内存充裕的今天已经无所谓了。


图像 编码

JPEG编码

  • 参考信息
    • https://zhuanlan.zhihu.com/p/145847377 : 海思MPP技术笔记

Base64编码

  • Base64 是一种编码方式,最早出现在电子邮件传输协议中。

    • 电子邮件问世之初,传递消息时只支持 ASCII 字符,后来随着电子邮件的广泛使用,传递非ASCII字符内容的需求增加,例如:传输中文、传输文件(图片、视频)。
    • 为解决这一问题,最好的方案是在不改变传输协议的基础上,做一种扩展方案来支持非ASCII内容传输,
    • 把非 ASCII 字符用ASCII来表示,Base64编码应运而生
  • Base64 是一种基于64个 ASCII 字符来表示二进制数据的表示方法

  • Base64 将8比特位为一个单元的字节数据拆分为以6个比特位为一个单元的二进制片段,每6个比特位单元对应Base64索引表中的一个字符,这样最终构成一个超过编码前字节数据33%的字符串。

  • Base64 中64个可打印字符包括字母A-Z、a-z、数字0-9,此外还有两个字符为+和/,这样构成了共有64字符的Base64索引表

    • base64-map
  • 为什么一些Base64后的字符中末尾有“==”

    • 编码前字节数正好被3整除,转化为二进制ASCII 编码( 3*8=24 )后,正好可以被6整除。
    • 若编码前字节数不能被3整除,最后会余出1个或2个字节,那么编码时需要:
      • 使用 000000 字节值在末尾补足,使其字节数能够被3整除;
      • 编码时补位的6个比特位单元用 = 表示。

视频基本概念

容器

  • 熟悉的mp4, rmvb, mkv, avi是多媒体容器文件格式(或称多媒体封装格式),所谓容器,**是指将不同的数据流(视频流,音频流,字幕流)封装在一个文件(载体)中。
  • 播放时各种流分别进行解码等处理,然后输出到显示器和音响等设备进行播放。多媒体容器格式不同于编码格式,一个容器中可以封装多种编码格式的媒体流。
  • 流封装了实际的媒体数据,例如视频流,音频流和字幕流等。一般情况下,流中的数据只能使用一种编码格式。

帧率

  • 帧率(frames per second, fps),是每秒画面刷新的次数,帧率越高视频越流畅。
  • 一般来说,30fps就是可以接受的,60fps则可以明显提升交互感,但一般超过75fps就不容易察觉到有明显的流畅度提升了

分辨率

  • 分辨率表示画面的精细程度,通常用像素密度来表示,常用的单位为ppi(像素每英尺),通常像素密度越高画面越精细,模糊程度越低
  • 对于视频文件而言,像素密度是无法控制的(由播放器和显式设备决定)。我们通常用视频的向素数来表示它的分辨率,例如1080x640, 640x320

比特率

  • 比特率(bit rate),又称码率,表示多媒体流每秒输出的字节数,单位为KB/s, Kbps等,同样的压缩算法,比特率越高音视频的质量越好。
  • 可变码率(Variable Bitrate, VBR),指的是编码其的输入码率可以根据输入源信号的复杂度进行自适应调整,以在输出质量保持不变的条件下尽可能减少数据量。VBR适用于存储,不太使用于流式传输。
  • 固定码率(Constant Bitrate, CBR), 指的是编码器输出码率固定,CBR不适合存储,对于复杂内容可能没有足够码率进行编码,从而导致质量下降,同时会在简单内容部分浪费一些码率

采样率

  • 采样率,指的是每秒钟对音频信号的采样次数,采样频率越高,声音还原度越高,声音更加自然,单位是赫兹Hz
  • 音频文件一般使用的采样率是44.1kHz,也就是一秒钟采样44100次,实验发现低于这个值就会由较明显的损失,而高于这个值人的耳朵已经很难分别,而且增加了数字音频所占的空间

视频编码

  • 视频流可以看作图片的序列,我们把这个序列中的一张图片称为一帧。若存储视频中所有的帧,则会导致数据量过大,不便于存储和传输。
  • 统计表明大多数视频相邻帧之间的区别并不大,所以对于一段变化不大的视频,我们可以先完整编码帧A,其后的B帧只需要编码与A帧不同的部分,B帧后的C帧则只编码与B帧的差异。如此递推,将一段视频编码为一个序列。
  • 当某个图像与之前的图像变化很大,导致无法参考前面的帧来生成,就结束上一个序列,并且将该帧完整编码开始一个新的序列
  • H264是目前流行的一种视频编码算法,它定义了三种帧:完整编码的I帧,参考I帧只包含差异的P帧,以及参考前后帧编码的B帧
  • H264采用的核心算法是帧内压缩和帧间压缩。
    • 帧内压缩是生成I帧的算法
    • 帧间压缩是生成B帧和P帧的算法
  • 通常,把完成编码的I帧称为关键帧。因为解码非关键帧需要解码其参考的帧,因此在截图等不需要全部解码的操作中,经常截取关键帧以提升性能。

视频基础知识和视频格式

  • 网站上的视频,是常说的网络流媒体
  • 将视频缓存到本地成一个文件,是常说的本地视频文件

视频封装格式(简称视频格式,也称为容器)

  • 视频格式是视频播放软件为了能够播放视频文件而赋予视频文件的一种识别符号

  • 换句话讲,视频格式规定了和播放器的通信协议

  • 首先,MP4, AVI, MKV等都是本地视频文件的后缀,在wiindows系统下,用于提示操作系统应该采用哪个应用程序打开。

  • 而在流媒体领域,这些都被称为视频封装格式,因为除了音视频流外,它们还包含了一些辅助信息以及组织音频的方式。

  • 不同格式的视频在不同平台上用户体验不同,很大原因在于对音视频的组织方式带来的差异。

  • 视频封装格式,是在编码的音视频基础上进行一次“包装”,添加与播放相关的协议数据。(不一定正确)

视频协议

  • 视频协议,是针对网络流媒体而言的,也就是只有在有网络时通过浏览器或者移动端APP才能看到的视频,目前常见的协议有RTSP, RTMP, HLS,HTTP等

  • 有的文章会把视频协议归入视频封装格式,因为它们都同时携带了音视频和metadata,以及协议/格式需要的其他信息。

  • 以FFMpeg为例,它并不区分视频格式和视频协议。

视频流

  • 常见的词语有:

    • h264码流,yuv流,编码流,解码流,原始流,裸流,或者 未压缩的流
  • 归纳的讲,视频流,一定只有两种形式

    • 经过压缩算法压缩的流数据,称为编码流,又因为目前压缩/编码算法以H264为主,因此常常称为H264码流
    • 未经过压缩的流数据,是解码后的流数据,称为原始流,可以想象视频是由一幅一幅在时间上连续的“图像”组成的,而因为视频内部的“图像”是YUV,因此也常常称为YUV流
  • 总结

    • h264码流,压缩后的流, 编码流 : 是压缩/编码后的视频流
    • yuv流,解码流,未压缩的流 : 是未经过压缩/编码的视频流
    • 裸流,是一个具有歧义的词,是上下文内容,既可以是前者,也可以是后者
  • 因此,在阅读任何流媒体相关的文章时,看到视频流都应该搞清楚,是编码/压缩的,还是没有

    • 在生活中,接触到的视频文件绝大部分都是编码/压缩后的
    • 在网络传输场景中,绝大部分也是编码/压缩后的。
    • 只有在视频播放时,看到的是一帧帧被转码为RGB的解码后的视频流
  • 编码/压缩在流媒体领域是一项非常重要的技术:

    • H264码流YUV流的过程称为解码,反之称为编码

  • 流媒体领域,很重要,流的基本元素同样重要。

  • 对于视频编码/压缩而言,它的核心是采用尽量小的空间存储一组时间上连续的帧数据

  • 而对于视频解码而言,就是把被编码/压缩后的一组数据尽量恢复成原来的样子。

    • 能够被100%恢复的编码/压缩算法称为无损压缩,反之称为有损压缩
  • 帧,可以联想成平时看到的一幅幅“图像”,只不过我们平时接触的图片是RGB格式的,而视频帧通常是YUV格式

  • 帧,为什么采用YUV格式?YUV是什么?

    • 在达到最大压缩率的情况下,能够保证对人眼感知的失真度最小。YUV的三个通道中,其中Y表示明亮度,也就是灰阶值;而U和V表示的则是色度。科学家研究发现,人眼对UV的敏感度最低,因此可以大比例地压缩UV两个通道的数值
    • 为了向前兼容黑白电视
  • YV12, YU12, NV12, NV21等,统称为视频的存储格式,也就是说,计算机是如何存储一帧视频的

  • 视频编解码而衍生的帧名词

    • I帧, P帧, B帧和IDR帧
    • GOP, Group Of Pictures,一般来说,指的是两个I帧之间的间隔
    • PTS, Presentation Time Stamp, 显示时间戳,它用来告诉播放器该什么时候显示这一帧的数据
    • DTS, Decoding Time Stamp, 解码时间戳,它的意义在于告诉解码器在什么时候解码这一帧的数据

JPG-JPEG(JFIF) 文件解码-文件结构

  • 参考链接:https://blog.csdn.net/ymlbright/

  • JPEG文件使用的数据存储方式有多种。最常用的格式称为JPEG文件交换格式(JPEG File Interchange Format,JFIF

  • 而JPEG文件大体上由一个个数据段组成,数据段包含:标记码(Tag)、数据长度、数据

  • 标记码由两个字节构成,其前一个字节是固定值0xFF,后一个字节则根据不同意义有不同数值

  • 在每个标记码之前还可以添加数目不限的无意义的0xFF填充,也就说连续的多个0xFF可以被理解为一个0xFF,并表示一个标记码的开始。而在一个完整的两字节的标记码后,就是该标记码对应的压缩数据流,记录了关于文件的诸种信息。

  • 常用的标记有SOI、APP0、DQT、SOF0、DHT、DRI、SOS、EOI

  • 注意,SOI等都是标记的名称。在文件中,标记是以标记码形式出现的。例如SOI的标记代码为0xFFD8,即在JPEG文件中的如果出现数据0xFFD8,则表示此处为一个SOI标记

    • SOI (0xFFD8) – 代表JFIF图像数据的开始
    • APP0 (0xFFE0) – 应用程序标记 0
    • APPn (0xFFEn) – 拓展应用程序标记 2~15, 为其他应用程序保留
    • DQT (0xFFDB) – 量化表,存储了对扫描数据进行量化的 8*8 矩阵
    • SOFx (0xFFCx) – 图像帧开始
    • DHT (0xFFC4) – Huffman表,存储了对扫描数据进行压缩的Huffman表,共4张,DC直流2张,AC交流2张
    • SOS (0xFFDA) – 扫描数据开始
    • scanData – 图像的压缩数据,为了不与之前的标记码(Tag)混淆,数据中遇到 0xFF 时,需要进行判断
      • 0xFF00:表示 0xFF 是图像数据的组成部分
      • 0xFFD0~0xFFD7:RSTn标记,遇到标记时,对差分解码变量进行重置(归0)
      • 0xFFD9:图像结束标记,图像压缩数据至此结束
    • EOI (0xFFD9) – 代表JFIF图像数据的结束,即文件结尾

腾讯 开发者社区

  • 文章链接:https://cloud.tencent.com/developer/article/1385273

  • 就音频而言,无论是算法多样性,Codec种类还是音频编解码复杂程度都远远比视频要高。

  • 视频的Codec目前还主要是以宏块为处理单元,预测加变换的混合编码框架,例如H.264和H.265都是在这一框架下

直播和点播

  • 从广义上来讲,直播和点播都是一种视频播放场景

  • 如果想要简单地区分二者,确实可以通过判断当前播放的视频画面是不是实时的来区分

    • 如果是实时的画面就是直播,
    • 如果不是实时的画面就是点播
  • 直播

    • 视频直播播放的视频内容是实时的视频画面,视频源是实时的媒体流。
    • 视频直播的播放内容稍纵即逝,无法回退和快进。
    • 日常生活中的视频直播场景非常多,比如直播带货、视频会议、赛事直播等
  • 点播

    • 视频点播播放的视频内容是非实时的视频画面,
    • 视频源是已经存在的视频文件或者媒体源,可以多次使用,可以回退和快进。
    • 日常生活中的视频点播场景也非常多,比如有线电视、网络点播、短视频等
  • 直播和点播的工作流程

    • 视频直播会涉及一个比较完整的视频处理流程,包括视频画面和声音采集、视频编码、组包发送、网络传输、收包解包、视频解码、视频渲染和声音播放等
    • 视频点播包括的流程就比较少了,一般只涉及文件读取、网络传输、视频解码、视频渲染和声音播放等流程,不会涉及视频画面和声音采集、视频编码、组包
  • 技术架构

    • 视频直播,常见的低延时方案大多是 RTC (Real time communication)方案,比如 WebRTC;大会直播类的场景一般是 CDN 方案,常用 rtmp、hls 等流媒体协议方案
    • 视频点播,常用的有电视信号和网络协议,比如 http,https 等,视频格式有 m3u8、mp4、flv、mkv、mxf 等。由于上述网络协议和传输信号的差异,视频直播和视频点播的播放器方案有所不同,也是二者的显著差异之一

RTC

什么是RTC?

  • RTC(Real time communication)实时通信,是实时音视频的一个简称,我们常说的RTC技术一般指的是WebRTC技术,已经被 W3C 和 IETF 发布为正式标准

  • 由于几乎所有主流浏览器都支持 WebRTC 标准 API ,因此也让浏览器之间无插件化的音视频互通成为可能, 大大降低了音视频开发的门槛,开发者只需要调用 WebRTC API 即可快速构建出音视频应用

  • 更广义的RTC技术,不单单局限于音视频,包括IM(Instant messaging,即时通讯)、图片、白板、文件共享等富媒体在内的实时交互也属于RTC技术范畴。

一套完善的RTC服务应用的技术

  • RTMP只是TCP上的一个标准协议,所以接入是一个标准体系,推流端可以是OBS( Open Broadcaster Software)这种直播软件工具,也可自开发rtmp推流工具,播放端可以是Flash播放器(Adobe 2020 12月份已经弃用)、服务端有技术成熟的CDN(Content Delivery Network,即内容分发网络)技术和设施进行分发、Native的播放器或者flv.js/hls.js这种开源播放器组件,遵循rtmp、flv、hls标准即可,接入成本比较低。而一个完善的RTC服务应用,需要从推流端、服务端、到拉流端,一整套完整的全链路闭环技术。

  • 互动连麦+服务端转推rtmp至CDN,CDN分发给观众。

简介

  • cuda编程中常见语法和关键词

__global__修饰符

  • 修饰符__global__告诉编译器这个函数将会从CPU中调用,然后在GPU上执行。示例如下:
    1
    2
    3
    4
    __global__ void helloFromGPU(void)
    {
    printf("hello world from GPU!\n);
    }

启动内核函数

1
helloFromGPU <<<1, 10>>>();
  • 三重尖括号意味着从主线程到设备端代码的调用。一个内核函数通过一组线程来执行,所有线程执行相同的代码。
  • 三重尖括号里面的参数是执行配置,用来说明使用多少线程来执行内核函数。在这个例子中,有目10个GPU线程被调用

cudaDeviceReset() 函数

  • cudaDeviceReset()函数用来显式地释放和清空当前进程中与当前设备有关的所有资源

简介

  • cuda相关的理论基础

cuda安装

  • 从NVIDIA官网CUDA下载页面: https://developer.nvidia.com/cuda-toolkit-archive 点击CUDA Toolkit 11.0.2下载相应版本的CUDA11.0.2。

  • 依次选择 Linux, x86_64, Ubuntu, 20.04。然后弹出三种安装方法,根据安装经验,推荐采用runfile(local)方法。这是由于CUDA的安装过程中需要很多依赖库文件,CUDA的run文件虽然比另外两种安装方法的文件大,但是他包含了所有的依赖库文件,所以相对来说很容易安装成功。

  • 在安装CUDA之前需要先安装一些相互依赖的库文件

    1
    sudo apt-get install freeglut3-dev build-essential libx11-dev libxmu-dev libxi-dev libgl1-mesa-glx libglu1-mesa libglu1-mesa-dev
  • 下面为安装CUDA11.0.2的Ubuntu安装指令

    1
    2
    wget https://developer.download.nvidia.com/compute/cuda/11.0.2/local_installers/cuda_11.0.2_450.51.05_linux.run
    sudo sh cuda_11.0.2_450.51.05_linux.run
  • 运行上面指令后,会弹出如下界面,点击Continue,然后再输入accept

  • 接着,如下图所示,在弹出的界面中通过Enter键,取消Driver和450.51.05的安装,然后点击Install,等待

  • 配置CUDA的环境变量

    • CUDA安装完成之后,需要配置变量环境才能正常使用,在.bashrc文件的最后添加以下CUDA环境变量配置信息
      1
      2
      3
      export PATH=$PATH:/usr/local/cuda/bin  
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64
      export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/cuda/lib64

cudnn安装

  • 安装CUDA对应的cudnn 网址: https://developer.nvidia.com/rdp/cudnn-download
  • 对下载的 cudnn-11.0-linux-x64-v8.0.5.39.tgz 进行解压操作,得到一个文件夹cuda,命令为
    1
    tar -zxvf cudnn-11.0-linux-x64-v8.0.5.39.tgz
  • 然后,使用下面两条指令复制cuda文件下的文件到 /usr/local/cuda/lib64和 /usr/local/cuda/include/ 中
    1
    2
    cp lib/* /usr/local/cuda/lib64/
    cp include/* /usr/local/cuda/include/
  • 拷贝完成之后,我们可以使用如下的命令查看cuDNN的信息
    1
    cat /usr/local/cuda-11.0/include/cudnn_version.h | grep CUDNN_MAJOR -A 2

cuda C 编程

  • CPU编程和GPU编程的主要区别是程序员对GPU架构的熟悉程度。用并行思维进行思考并对GPU架构有了基本的了解,会使你编写规模达到成百上千个核的并行程序,如同写穿行程序一样简单

  • CUDA中有内存层次和线程层次的概念,使用如下结构有助于你对线程进行更高层次的控制和调度

    • 内存层次结构
    • 线程层次结构
  • CUDA抽象了硬件细节,且不需要将应用程序映射到传统图形API上。CUDA核中有三个关键抽象

    • 线程组的层次结构
    • 内存的层次结构
    • 障碍同步
  • 目标应该是学习GPU架构的基础及掌握CUDA开发工具和环境

  • CUDA开发环境。NVIDIA为C和C++开发人员提供了综合的开发环境以创建GPU加速应用程序,包括以下几种

    • NVIDIA Nsight集成开发环境
    • CUDA—GDB命令行调试器
    • 用于性能分析的可视化和命令行分析器
    • CUDA—MEMCHECK内存分析其
    • GPU设备管理工具

cuda编程结构

  • 一个典型的CUDA编程结构包括五个主要步骤

    • 分配GPU内存
    • 从CPU内存拷贝数据到GPU内存
    • 调用CUDA内核函数来完成程序指定的运算
    • 将数据从GPU拷回CPU内存
    • 释放GPU内存空间
  • CUDA编程模型使用由C语言扩展生成的主时代码在异构计算系统中执行应用程序。

  • 在一个异构环境中包含多个CPU和GPU,每个GPU和CPU的内存都由一条PCI-Express总线分隔开。因此需要注意区分以下内容

    • 主机: CPU及其内存(主机内存)
    • 设备: GPU及其内存(设备内存)
  • 从CUDA6.0开始,NVIDIA提出了名为”统一寻址(Unified Memory)”的编程模型的改进,它连接了主机内存和设备内存空间,可使用单个指针访问CPU和GPU内存,无序彼此之间手动拷贝数据。

  • 现在,主要的是应学会如何为主机和设备分配内存空间以及如何在CPU和GPU之间拷贝共享数据。这种程序员管理模式控制下的内存和数据可以优化应用程序并实现硬件系统利用率的最大化

  • 内核(kernel)是CUDA编程模型的一个重要组成部分,其代码在GPU上运行。作为一个开发人员,你可以串行执行核函数。在此背景下,CUDA的调度管理程序员在GPU线程上编写核函数。在主机上,基于应用程序数据以及GPU的性能定义如何让设备实现算法功能。这样做的目的是使你专注于算法的逻辑(通过编写串行代码),且在创建和管理大量的GPU线程时不必拘泥于细节。

  • CUDA编程模型主要是异步的,因此在GPU上进行的运算可以与主机-设备通讯重叠。一个典型的CUDA程序包括由并行代码互补的串行代码。

    • 串行代码(及任务并行代码)在主机CPU上执行,而并行代码在GPU上执行。
    • 主机代码按照ANSI C 标准进行编写,而设备代码使用CUDA C进行编写
    • 你可以将所有的代码统一放在一个源文件中,也可以使用多个源文件来构建应用程序和库
    • NVIDIA的C编译器(nvcc)为主机和设备生成可执行代码

cuda 内存管理

  • CUDA编程模型假设系统是由一个主机和一个设备组成的,而且各自拥有独立的内存。核函数是在设备上运行的。为使你拥有充分的控制权并使系统达到最佳性能,CUDA运行时负责分配和释放设备内存,并且在主机内存和设备内存之间传输数据

  • 用于执行GPU内存分配的是cudaMalloc函数,其函数原型为:

    1
    cudaError_t cudaMalloc(void** devPtr, size_t size)
  • 该函数负责向设备分配一定字节的线性内存,并以devPtr的形式返回指向锁分配内存的指针。

  • cudaMalloc与标准C语言中的malloc函数几乎一样,只是次函数在GPU的内存里分配内存。

  • cudaMemcpy函数负责主机和设备之间的数据传输,其函数原型为:

    1
    cudaError_t cudaMemcpy(void* dst, const void* src, size_t count, cudaMemcpyKind kind)
  • 次函数从src指向的源存储区复制一定数量的字节到dst指向的目标存储区。复制方向由kind指定,其中的kind有以下几种

    • cudaMemcpyHostToHost
    • cudaMemcpyHostToDevice
    • cudaMemcpyDeviceToHost
    • cudaMemcpyDeviceToDevice
  • 这个函数以同步方式执行,因为在cudaMemcpy函数返回以及传输操作完成之前主机应用程序是阻塞的

  • 除了内核启动之外的CUDA调用都会返回一个错误的枚举类型cudaError_t

    • 如果GPU内存分配成功,函数返回cudaSuccess
    • 否则返回cudaErrorMemoryAllocation
  • 可以使用CUDA运行时函数将错误代码转化为可读的错误消息

    1
    char* cudaGetErrorString(cudaError_t error)
  • 在GPU内存层次结构中,最主要的两种内存是全局内存和共享内存。

    • 全局内存类似于CPU的系统内存
    • 共享内存类似于CPU的缓存
  • GPU的共享内存可以由CUDA C的内核直接控制

cuda 线程管理

  • 当核函数在主机端启动时,它的执行会移动到设备上,此时设备中会产生大量的线程并且每个线程都执行由核函数指定的语句。

  • 由一个内核启动所产生的所有线程统称为一个网格。同一网格中的所有线程共享相同的全局内存空间。

  • 一个网格由多个线程块构成,一个线程块包含一组线程,同一线程快内的线程写作可以通过以下方式来实现

    • 同步
    • 共享内存
  • 不同块内的线程不能协作

  • 线程依靠以下两个坐标变量来区分彼此

    • blockIdx(线程块在线程格内的索引)
    • threadIdx(块内的线程索引)
  • 这些变量是核函数中需要预初始化的内置变量。当执行一个核函数时,CUDA运行时为每个线程分配坐标变量blockIdx和threadIdx。基于这些坐标,你可以将部分数据分配给不同的线程

启动一个CUDA核函数

  • CUDA内核调用是对C语言函数调用语句的延伸,<<<>>>运算符内是核函数的执行配置

    1
    kernel_name <<<grid, block>>>(argument list);
  • 利用执行配置可以指定线程在GPU上调度运行的方式。执行配置的第一个值是网格维度,也就是启动块的数目。第二个值是块维度,也就是每个块中线程的数目。通过指定网格和块的维度,你可以进行以下配置:

    • 内核中线程的数目
    • 内核中使用的线程布局
  • 同一个块中的线程之间可以相互协作,不同块内的线程不能协作。

  • 核函数的调用与主机线程是异步的。核函数调用结束后,控制权立刻返回给主机端。你可以调用以下函数来强制主机端程序等待所有的核函数执行结束

    1
    cudaError_t cudaDeviceSynchronize(void);
  • 一些cuda运行时API在主机和设备之间是隐式同步的。当使用cudaMemory函数在主机和设备之间拷贝数据时,主机端隐式同步,即主机端程序必须等待数据拷贝完成后才能继续执行程序。

  • 不同于C语言的函数调用,所有的CUDA核函数的启动都是异步的。CUDA内核调用完成后,控制权立刻返回给CPU

编写核函数

  • 核函数是在设备端执行的代码。在核函数中,需要为一个线程规定要进行的计算以及要进行的数据访问。当核函数被调用时,许多不同的CUDA线程并行执行同一个计算任务。以下是用__global__声明定义核函数

    1
    __global__ void kernel_name(argument list);
  • 函数类型限定符指定一个函数在主机上执行还是在设备上执行,以及可被主机调用还是被设备调用

    • global : 在设备端执行 可从主机端调用,也可以从计算能力为3的设备中调用 必须有一个void返回类型
    • device : 在设备段执行 仅能从设备端调用
    • host : 在主机端执行 仅能从主机端调用
  • __device__和__host__限定符可以一起使用,这样函数可以同时在主机和设备端进行编译

  • CUDA核函数的限制

    • 只能访问设备内存
    • 必须具有void返回类型
    • 不支持可变数量的参数
    • 不支持静态变量
    • 显示异步行为

CUDA 处理错误

  • 由于许多CUDA调用是异步的,所以有时可能很难确定某个错误是由哪一步程序引起的。使用宏可以检查核函数的错误

    1
    CHECK(cudaMemory(d_c, gpuRef, nBytes, cudaMemcpyHostToDevice));
  • 如果内存拷贝或之前的异步操作产生了错误,这个宏会报告错误代码并输出一个可读信息,然后停止程序。

  • 也可以用下述方法,在核函数调用后检查核函数错误

    1
    2
    kernel_function<<<grid, block>>>(argument list);
    CHECK(cudaDeviceSynchronize());
  • CHECK(cudaDeviceSynchronize())会阻塞主机端线程的运行,直到设备端所有的请求任务都结束,并确保最后的核函数启动部分不会出错。

用nvprof工具计时

  • 自CUDA5.0以来,NVIDIA提供了一个名为nvprof的命令行分析工具,可以帮助从应用程序的CPU核GPU活动情况中获取时间线信息,其包括内核执行,内存传输以及CUDA API的调用,其用法如下
    1
    nvprof [nvprof_args] <application> [application_args]

设备管理

  • NVIDIA提供了几个查询和管理GPU设备的方法。学会如何查询GPU设备信息是很重要的,因为在运行时你可以使用它来帮助设置内核执行配置

  • 两种方法学习查询和管理GPU设备

    • CUDA运行时API函数
    • NVIDIA系统管理界面(nvidia-smi)命令行程序
  • 使用以下函数查询关于GPU设备的所有信息

    1
    cudaError_t cudaGetDeviceProperties(cudaDeviceProp* prop, int device);
  • cudaDeviceProp结构体返回GPU设备的属性。

  • 使用nvidia-smi查询GPU信息

    • 要确定系统中安装了多少个GPU以及每个GPU的设备ID,可以使用以下命令
      1
      nvidia-smi -L
    • 使用以下命令获取GPU 0的详细信息
      1
      nvidia-smi -q -i 0

cuda cudnn tensorrt 之间存在什么关系

CUDA(Compute Unified Device Architecture)、cuDNN(CUDA Deep Neural Network)、和TensorRT(TensorRT)都是与GPU计算和深度学习相关的 NVIDIA 技术。

  1. CUDA(Compute Unified Device Architecture):

    • CUDA 是 NVIDIA 提供的并行计算平台和编程模型,允许开发者使用通用编程语言(如C/C++和Fortran)来编写支持GPU加速的程序。
    • CUDA 提供了 GPU 计算的基础设施,包括 GPU 内核函数、内存管理、线程调度等。
  2. cuDNN(CUDA Deep Neural Network):

    • cuDNN 是 NVIDIA 提供的深度学习库,专门用于 GPU 加速的深度神经网络训练和推理。
    • 它优化了深度学习算法的实现,提供了高性能的 GPU 实现,包括卷积、池化、归一化等操作,以加速深度学习模型的训练和推理过程。
  3. TensorRT(Tensor Runtime):

    • TensorRT 是 NVIDIA 提供的用于高性能深度学习推理的库。它通过对模型进行优化和精简,以减少推理时的延迟和提高推理性能。
    • TensorRT 可以与 cuDNN 结合使用,对深度学习模型进行加速,同时还支持 INT8 等低精度推理。

关系:

  • CUDA 提供了底层的 GPU 计算基础架构,cuDNN 利用 CUDA 提供的加速功能实现深度学习操作的高性能实现。
  • TensorRT 则建立在 CUDA 和 cuDNN 的基础上,专注于优化深度学习模型的推理过程,通过减少模型的计算负载和内存占用来提高推理性能。 TensorRT 还可以与 cuDNN 配合使用,以进一步优化深度学习模型的推理。

总体而言,CUDA 提供了底层的 GPU 编程能力,cuDNN 是在 CUDA 上构建的深度学习库,而 TensorRT 是专注于优化深度学习推理的高性能库。

C++ cuda库是什么

在C++中,”CUDA库”通常指的是NVIDIA提供的CUDA(Compute Unified Device Architecture)工具包,它是用于并行计算的一组工具和API。CUDA库使得开发人员能够在NVIDIA GPU上进行通用目的的并行计算。

CUDA库包括以下主要组件:

  1. CUDA Runtime库: 提供了一系列C语言风格的API,允许在CUDA设备(GPU)上执行并行计算。开发人员使用这些API来管理设备内存、启动GPU内核函数等。

  2. CUDA Driver库: 提供了与硬件交互的低级别接口,允许对GPU进行更直接的控制。一般情况下,开发人员更常用CUDA Runtime库,而不是直接使用Driver库。

  3. cuBLAS: 针对线性代数运算的CUDA库,提供了一组高性能的基本线性代数子程序(BLAS)。

  4. cuFFT: 针对快速傅里叶变换(FFT)的CUDA库,用于高性能的FFT计算。

  5. cuDNN: 深度神经网络库,提供了一组高性能的深度学习基本操作和算法,用于在GPU上进行深度学习推理和训练。

  6. NVRTC(NVIDIA Runtime Compiler): 允许在运行时将CUDA C源代码编译为GPU代码的库。

在使用CUDA库进行开发时,通常需要安装NVIDIA的GPU驱动、CUDA工具包以及适当版本的cuBLAS、cuFFT等库。开发人员可以使用CUDA C/C++编写GPU内核函数,并通过调用CUDA库中的API来执行并行计算任务。

以下是一个简单的示例,展示了在C++中使用CUDA Runtime库执行向量加法的基本步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>

// CUDA kernel函数,执行向量加法
__global__ void vectorAddition(float* a, float* b, float* result, int size) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < size) {
result[idx] = a[idx] + b[idx];
}
}

int main() {
const int size = 1024;
const int blockSize = 256;

// 分配主机内存
float* hostA = new float[size];
float* hostB = new float[size];
float* hostResult = new float[size];

// 初始化输入数据
for (int i = 0; i < size; ++i) {
hostA[i] = i;
hostB[i] = 2 * i;
}

// 分配设备内存
float* deviceA, *deviceB, *deviceResult;
cudaMalloc(&deviceA, size * sizeof(float));
cudaMalloc(&deviceB, size * sizeof(float));
cudaMalloc(&deviceResult, size * sizeof(float));

// 将输入数据从主机复制到设备
cudaMemcpy(deviceA, hostA, size * sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy(deviceB, hostB, size * sizeof(float), cudaMemcpyHostToDevice);

// 启动CUDA kernel
vectorAddition<<<(size + blockSize - 1) / blockSize, blockSize>>>(deviceA, deviceB, deviceResult, size);

// 将结果从设备复制到主机
cudaMemcpy(hostResult, deviceResult, size * sizeof(float), cudaMemcpyDeviceToHost);

// 打印结果
for (int i = 0; i < 10; ++i) {
std::cout << hostResult[i] << " ";
}

// 释放内存
delete[] hostA;
delete[] hostB;
delete[] hostResult;
cudaFree(deviceA);
cudaFree(deviceB);
cudaFree(deviceResult);

return 0;
}

上述示例使用CUDA Runtime库执行向量加法,其中CUDA kernel函数在GPU上并行执行。请注意,CUDA开发涉及到更多的细节和概念,这里只是一个简单的入门示例。

C++ cuda库 详解

CUDA(Compute Unified Device Architecture)是由NVIDIA提供的用于通用目的的并行计算框架。它允许开发人员使用C++或CUDA C等编程语言在NVIDIA GPU上执行并行计算任务。以下是关于CUDA库的一些详细信息:

主要组件:

  1. CUDA Runtime库: 提供了一系列C语言风格的API,用于在CUDA设备上执行并行计算。这包括设备管理、内存分配和释放、CUDA核函数的启动等功能。开发人员通常使用这些API进行CUDA编程。

  2. CUDA Driver库: 提供了更低级别的接口,允许直接与硬件进行交互。一般情况下,开发人员更多地使用CUDA Runtime库,而不是直接使用Driver库。

  3. cuBLAS(CUDA Basic Linear Algebra Subroutines): 面向线性代数操作的库,提供高性能的基本线性代数子程序(BLAS)。

  4. cuFFT(CUDA Fast Fourier Transform): 针对快速傅立叶变换(FFT)的库,用于高性能的FFT计算。

  5. cuRAND: 提供随机数生成功能的库,包括伪随机数生成器、分布等。

  6. cuSPARSE: 面向稀疏矩阵操作的库,提供高性能的稀疏矩阵运算。

  7. NVRTC(NVIDIA Runtime Compiler): 允许在运行时将CUDA C源代码编译为GPU代码的库。这对于动态生成GPU核函数是有用的。

CUDA编程模型:

CUDA编程模型涉及在主机(CPU)和设备(GPU)之间进行数据传输,以及在GPU上并行执行核函数。以下是CUDA编程的一般步骤:

  1. 分配设备内存: 使用cudaMalloc等CUDA Runtime函数在GPU上分配内存。

  2. 将数据从主机复制到设备: 使用cudaMemcpy将数据从主机复制到GPU。

  3. 定义并启动CUDA核函数: 使用__global__修饰符定义CUDA核函数,并使用<<<...>>>语法启动并行执行。

  4. 将数据从设备复制回主机: 使用cudaMemcpy将计算结果从GPU复制回主机。

  5. 释放设备内存: 使用cudaFree等函数释放在GPU上分配的内存。

示例代码:

以下是一个简单的示例代码,展示了在CUDA中执行向量加法的基本步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>

__global__ void vectorAddition(float* a, float* b, float* result, int size) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < size) {
result[idx] = a[idx] + b[idx];
}
}

int main() {
const int size = 1024;
const int blockSize = 256;

float* hostA = new float[size];
float* hostB = new float[size];
float* hostResult = new float[size];

// 初始化输入数据...

float* deviceA, *deviceB, *deviceResult;
cudaMalloc(&deviceA, size * sizeof(float));
cudaMalloc(&deviceB, size * sizeof(float));
cudaMalloc(&deviceResult, size * sizeof(float));

cudaMemcpy(deviceA, hostA, size * sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy(deviceB, hostB, size * sizeof(float), cudaMemcpyHostToDevice);

vectorAddition<<<(size + blockSize - 1) / blockSize, blockSize>>>(deviceA, deviceB, deviceResult, size);

cudaMemcpy(hostResult, deviceResult, size * sizeof(float), cudaMemcpyDeviceToHost);

// 处理结果...

delete[] hostA;
delete[] hostB;
delete[] hostResult;
cudaFree(deviceA);
cudaFree(deviceB);
cudaFree(deviceResult);

return 0;
}

上述示例演示了在GPU上使用CUDA执行向量加法的步骤。这只是一个简单的入门示例,实际情况中可能涉及更复杂的计算任务和数据管理。确保参考CUDA文档和示例以获取更多详细信息。

C++ cuda库 编译和链接

编译和链接使用CUDA库的C++代码通常需要遵循以下步骤。以下示例假设你已经安装了NVIDIA的GPU驱动、CUDA工具包和相应版本的cuBLAS、cuFFT等库。

编写CUDA C++代码

首先,编写包含CUDA核函数的C++源代码。在CUDA C++代码中,通常使用__global__修饰符定义在GPU上执行的核函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// vectorAdd.cu
#include <iostream>

__global__ void vectorAddition(float* a, float* b, float* result, int size) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < size) {
result[idx] = a[idx] + b[idx];
}
}

int main() {
const int size = 1024;
const int blockSize = 256;

// ... 与上述示例相同的主机代码 ...

// 在主机上调用 CUDA 核函数
vectorAddition<<<(size + blockSize - 1) / blockSize, blockSize>>>(deviceA, deviceB, deviceResult, size);

// ... 处理结果及释放内存 ...
}

编译CUDA C++代码

使用nvcc命令来编译CUDA C++代码。nvcc是NVIDIA CUDA Compiler的命令行工具。

1
nvcc -o your_executable vectorAdd.cu

此命令将源文件vectorAdd.cu编译为可执行文件your_executablenvcc会负责将CUDA核函数编译为GPU二进制代码,并链接CUDA运行时库。

编译和链接其他C++文件

如果你的项目包含其他C++源文件,你可以使用常规的C++编译器(例如g++)来编译和链接这些文件。确保将CUDA头文件和库的路径包含到编译过程中,并链接相应的CUDA库。

1
g++ -o your_executable your_main.cpp -I/path/to/cuda/include -L/path/to/cuda/lib -lcudart -lcublas -lculibos -lcufft

在上述命令中,/path/to/cuda/include/path/to/cuda/lib需要替换为你系统中实际的CUDA头文件和库文件的路径。

运行可执行文件

最后,运行生成的可执行文件。

1
./your_executable

请注意,确保CUDA库和头文件的路径正确设置,并且使用了正确的编译选项。在实际项目中,可能需要更详细的配置和管理依赖项。

课程笔记

  • 超标量,Superschalar

  • 指令调度,Scheduling

  • 寄存器重命名,Register Renaming

  • 乱序执行,Out-of-Order(OoO)Execution

    • 重排指令,获得最大的吞吐率
    • 重排缓冲区
  • 存储器架构/层次,Memory Hierarchy

    • 存储器越大越慢
    • 粗略的估计
      • SRAM(L1, L2, L3)
      • DRAM(memory)
      • Flash(disk)
      • HDD(disk)
  • 缓存,Caching

    • 将数据放在尽可能接近的位置
    • 利用
      • 时间临近性,刚刚使用过的数据很可能会被再次使用
      • 空间临近性,倾向于使用周围的临近的数据
    • 缓存层次,Cache Hierarchy
      • 硬件管理
        • L1 Instruction/Data caches
        • L2 unified cache
        • L3 unified cache
      • 软件管理
        • Main memory
        • Disk
  • 存储器的另外设计考虑

    • 分区Banking – 避免多端口
    • 一致性 Coherency
    • 控制器 Memory Controller
  • CPU内部的并行性

    • 指令级并行
      • 超标量
      • 乱序执行
    • 数据级并行
      • 矢量计算
    • 线程级并行
      • 同步多线程
      • 多核
  • 向量运算,Vectors Motivation

  • 数据级并行 Data=level Parallelism

    • 单指令多数据 Single Instruction Multiple Data(SIMD)
      • 执行单元(ALU)很宽
  • 线程级并行 Thread-Level-Parallelism

  • 多核 Multicore

    • 将流水线完整复制
  • 锁存,一致性和同一性

    • 问题:多线程读写同一块数据
      • 解决办法:加锁
    • 问题:谁的数据是正确的?
      • 解决办法:缓存一致性协议Coherence
    • 问题:什么样的数据是正确的Consistency
      • 解决方法:存储器同一性模型
  • 现实的困境:能量墙 Power Wall

  • 新时代的计算计数:并行计算

    • 常规传统单核处理器遇到物理约束,时钟频率(perf/clock)无法保持线性增长
  • 新摩尔定律

    • 处理器越来越胖
    • 单核的性能不会大幅度提升
  • 另外一堵墙:存储器墙

    • 处理器的存储器带宽无法满足处理能力的提升
  • 结论

    • CPU,为串行程序优化
      • Pipelines, branch prediction, superscalaer, OoO
      • Reduce execution time with high clock speeds and high utilization
    • 缓慢的内存带宽(存储器带宽)将会是大问题
    • 并行处理是方向

并行技术的概述

  • (数据)并行处理方法

  • 串行计算模式

    • 常规软件是串行的
      • 设计运行于一个中央处理器上(CPU)
      • 通过离散的指令序列完成一个问题的解决
      • 一条一条指令的执行
      • 同时只有一条指令在执行
  • 并行计算模式

    • 一句话:并行计算是同时应用多个计算资源解决一个计算问题
      • 设计多个计算资源或处理器
      • 问题被分解为多个离散的部分,可以同时处理(并行)
      • 每个部分可以由一系列指令完成
    • 每个部分的指令在不同的处理器上执行
  • 概念和名词

    • Flynn 矩阵, The matrix below defines the 4 possible classifications according to Flynn
      • SISD, Single Instruction, Single Data
      • SIMD, Single Instruction, Multiple Data
      • MISD, Multiple Instruction, Single Data
      • MIMD, Multiple Instruction, Multiple Data
  • 常见名词

    • Task,任务 – 可以得到完整结果的一个过程,一个或多个代码段
    • Parallel Task , 并行任务
    • Serial Execution, 串行执行
    • Parallel Execution Execution,并行执行
    • Shared Memory,共享存储
    • Distributed Memory,分布式存储 – 存储的东西放在不同的地方
    • Communications,通信
    • Synchronization,同步 –
    • Granularity,粒度 – 划分任务的大小
    • Observed Speedup,加速比 – 对比一个标志物,获得性能的提升
    • Parallel Overhead,并行开销 – 最主要是通讯的问题
    • Scalability,可扩展性 –
  • 存储器架构

    • 共享存储(Shared Memory)
    • 分布式存储(Distributed Memory)
    • 混合分布式-共享式存储(Hybrid Distributed-Shared Memory)
  • 并行编程模型

    • 共享存储模型(Shared Memory Model)
    • 线程模型(Threads Model)
    • 消息传递模型(Message Passing Model)
    • 数据并行模型(Data Parallel Model)
  • 具体实例

    • OpenMP
    • MPI
    • Single Program Multiple Data(SPMD)
    • Multiple Program Multiple Data(MPMD)
  • 设计并行处理程序和系统

    • 自动和手动并行
    • 理解问题和程序
    • 分块分割
    • 通信
      • broadcast,广播
      • scatter,发散
      • gather,汇集
      • reduction,整合
    • 同步
    • 负载均衡 – 一核有难,七核围观
    • 粒度
    • I/O
    • 成本
    • 性能分析和优化
  • 加速比:

    • speedup = 1/(p/N + S)
      • P = 并行部分
      • N = 处理器数码
      • S = 串行部分

搭建并行编程环境

  • cuda zone

  • sample/devicequery

  • 名词解释

    • FLOPS – FLoating -point OPerations per Second
    • GFLOPS – One billion FLOPS
    • TFLOPS – 1000 GFLOPS
  • 为什么需要GPU?

    • 应用的需求越来越高
    • 计算机技术由应用推动
  • GPU(Graphic Processing Unit),是一个异构的多处理器芯片,为图形图像处理优化

  • 三种方法提升GPU的处理速度

  • GPU的存储器

  • 停滞

    • 大量的独立片元相互切换
    • 通过片元切换掩藏延迟
  • 上下文存储空间, Storing contexts

    • 上下文存储池
  • 上下文呢也可以软件也可以硬件管理

  • 如果只是一个ALU,只能称为一个计算单元,

  • ALU+存储,是一个计算核心,简称核

  • Fermi架构细节

    • 480 stream processors(“CUDA cores”)
      • 一个stream processor,流处理器,理解上可以等同为一个ALU,计算单元
  • 存储和数据访问 – 访存

    • Recall:”CPU-style” core
      • memory hierarchy, 多级缓存
    • GPU型的吞吐处理核
      • 将存储器放在了外面
  • 访存带宽,是非常宝贵的资源

  • 带宽测试 –

  • 带宽受限

    • 减少带宽需求
  • GPU,是异构 众核 处理器,针对吞吐优化

  • 高效的GPU任务具备的条件

    • 具有成千上万的独立工作
      • 尽量利用大量的ALU单元
      • 大量的片元切换掩藏延迟
    • 可以共享指令流
      • 适用于SIMD处理
    • 最好是计算密集的任务
      • 通信和计算开销比例合适
      • 不要受制于访问带宽

  • CPU-GPU交互

    • 各自的物理内存空间
    • 通过PCIE总线互连
    • 交互开销较大
  • 访存速度

    • Register
    • Shared Memory
    • Local Memory
    • Global Memory
    • Constant Memory
    • Texture Memory
    • Instruction Memory
  • 线程组织架构说明

    • 一个Kernel具有大量线程
    • 线程被划分成线程块 blocks
    • Kernel启动一个grid,包含若干线程块
    • 线程和线程块具有唯一的标识
  • 编程模型:

    • 常规意义的GPU用于处理图形图像
    • 操作于像素,每个像素的操作都类似
    • 可以应用SIMD,也可以认为是数据并行分割
  • CUDA编程模式:Extended C

  • CUDA函数声明

    • __global__定义一个kernel函数:入口函数,CPU上调用,GPU上执行,必须返回void

7

  • 2007 – NVIDIA发布CUDA

    • CUDA – 全称 Compute Uniform Device Architecture,统一计算设备架构
  • CUDA术语

    • Host – 即主机端,通常指CPU。采用ANSI标准C语言编程
    • Device – 即设备端,通常指GPU(数据可并行)。采用ANSI 标准C的扩展语言编程
    • Host和Device拥有各自的存储器
    • CUDA编程:包括主机端和设备端两部分代码
    • Kernel – 数据并行处理函数。通过调用kernel函数在设备端创建轻量级线程,线程由硬件负责创建并进行管理

简介

  • NVIDIA 相关理论基础知识

NVIDIA 驱动卸载

  • 运行命令:
    • sudo apt-get autoremove --purge nvidia-*

最新驱动安装

安装依赖

  • 安装驱动之前,必须要更新软件列表和安装依赖
    1
    2
    3
    4
    sudo apt-get update      #更新软件列表
    sudo apt-get install g++
    sudo apt-get install gcc
    sudo apt-get install make

禁用nouveau

  • nouveau是Ubuntu自带的显卡驱动,但他是核显,我这里想安装独显,就得把他禁掉。

  • 创建文件,如果没有下载vim编辑器,将vim换成gedit即可

    1
    $ sudo vim /etc/modprobe.d/blacklist-nouveau.conf
  • 在文件中插入以下内容,将nouveau加入黑名单,默认不开启

    1
    2
    blacklist nouveau
    options nouveau modeset=0
  • 输入以下命令使禁用生效然后重启

    1
    2
    sudo update-initramfs -u   #更新系统
    sudo reboot
  • 重启后验证

    1
    lsmod | grep nouveau
  • 如果回车后无反应,则禁用成功

先完全卸载之前的显卡驱动

  • ppa源文件卸载(方式一):

    1
    $ sudo apt-get remove --purge nvidia*
  • runfile源文件卸载(方式二):

    1
    $ sudo ./NVIDIA-Linux-x86_64-384.59.run --uninstall

安装显卡驱动

  • 查询电脑最适合的显卡驱动版本

    1
    ubuntu-drivers devices
  • 随后用命令行进行安装

    1
    2
    3
    4
    sudo add-apt-repository ppa:graphics-drivers/ppa
    sudo apt-get update
    sudo apt-get install nvidia-driver-525 #此处数字要对应上面查询到的版本号
    sudo apt-get install mesa-common-dev
  • 注意: 如果前面没有禁用secure boot,则在安装过程中会提示设置一个密码,在重启时需要输入密码验证以禁用secure boot,重启后会出现蓝屏,这时候不能直接选择continue,而应该按下按键,选择Enroll MOK, 确认后在下一个选项中选择continue,接着输入安装驱动时设置的密码,开机。

  • 安装完成后重启

    1
    sudo reboot
  • 重启后在终端验证

    1
    nvidia-smi

NVIDIA 驱动安装

  • Ubuntu下安装NVIDIA驱动的三种方法:

    • 使用标准Ubuntu仓库进行自动安装
    • 使用PPA仓库进行自动化安装
    • 使用官方的NVIDIA驱动进行手动安装
  • 第一种方法操作最为简单,方便,第三种方法是最稳定,最常用的。

  • 使用标准Ubuntu 仓库进行自动安装

    1
    2
    3
    4
    5
    sudo ubuntu-drivers devices

    sudo ubuntu-drivers autoinstall

    # 完成后重启 就可完成安装NVIDIA驱动
  • 使用PPA仓库进行自动安装

    1
    2
    3
    4
    5
    6
    7
    sudo add-apt-repository ppa:graphics-drivers/ppa      //添加ppa库到系统中
    sudo apt update // 更新

    sudo ubuntu-drivers devices // 显示可以安装的nvidia驱动


    sudo apt install nvidia-xxx // xxx 代表你想安装的nVidia驱动的版本号
  • 使用官方的NVIDIA驱动进行手动安装

    • 先要搞清楚你的nvidia显卡是什么什么型号
      1
      2
      3
      4
      5
      lshw -numeric -C display

      或者

      lspci -vnn | grep VGA // 查看nvidia显卡型号
    • 然后到NVIDIA官网下载对应你显卡型号的最新版本驱动进行下载 保存到你自己的路径文件夹
    • NVIDIA官网驱动下载地址: https://www.nvidia.com/zh-cn/ 进入后选择最上面的驱动程序 就可以自行选择自己的显卡
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      //   这种方法安装nvidia驱动需要先停止图形界面

      sudo telinit 3

      // 之后进入一个新的命令行会话,使用当前的用户名密码登陆

      cd 进入你放nvidia驱动的路径

      用 ./ 或者 bash 进行安装

      安装的过程如下:(按照以下步骤)

      Accept License

      The distribution-provided pre-install script failed! Are you sure you want to continue?
      CONTINUE INSTALLATION

      Would you like to run the nvidia-xconfig utility?
      YES

      之后执行
      sudo reboot // 重启
  • 上面三种方法结束后,需要检验是否安装好了nvidia驱动。

    1
    2
    3
    sudo reboot  // 安装完了驱动需要重启

    sudo nvidia-smi // 检验是否安装好驱动

查看显卡驱动

  • 查看显卡,在终端输入以下命令:

    1
    2
    lspci | grep -i vga
    lspci | grep -i nvidia
  • 查看显卡驱动在终端输入以下命令:

    1
    lsmod | grep -i nvidia

简介

  • nvcc是cuda程序的编译器

  • 了解它的一些关键参数有利于我们更加从容,更加准确的构建自己的CUDA项目

  • Nvcc是一种编译器驱动,通过命令行选项可以在不同阶段启动不同的工具完成编译工作,其目的在于隐藏了复杂的CUDA编译细节,

  • 并且它不是一个特殊的CUDA编译驱动而是在模仿一般的通用编译驱动如gcc,它接受一定的传统编译选项如宏定义,库函数路径以及编译过程控制等

cuda编译文件流程

预处理

  • 首先是对输入的cu文件有一个预处理过程,这一过程包括的步骤有将该源文件里的宏以及相关引用文件扩展开,然后将预编译已经产生的与C有关的CUDA系统定义的宏扩展开,并合并分支编译的结果
  • CUDA源文件经过预处理过程后会生成具有.cup后缀名的经过预处理的源文件,经过预处理的源文件是再现程序bug的最好形式。
  • 通常,这个预处理过程是隐式完成的,如果想要显示执行,可以用命令nvcc-E x.cu -o x.cupnvcc -E x.cu > x.cup来实现

cudafe – 前后端设备分离

  • 预处理后,首先将预处理的结果送给CUDA前端,即CUDAfe.通过CUDAfe分离源文件,然后调用不同的编译器分别编译
  • cudafe被称为CUDAfrontend,会被调用两次,完成两个工作:
    • 一是,将主机代码与设备代码分离,生成gpu文件
    • 二是,对gpu文件进行deadcode analysis,传给nvopencc,未来的版本可能没有第二次调用
  • Nvopencc生成ptx文件传给ptxas,最后将cubin或ptx传给fatbin组合成一个设备代码描述符

生成编译代码

  • 在编译阶段CUDA源代码对C语言所扩展的部分将被转成regular ANSIC的源文件,也就可以由一般的C编译器进行更多的编译和连接。也即是设备代码被编译成ptx(parallel threadexecution)代码和/或二进制代码,
  • host代码则以C文件形式输出,在编译时可将设备代码描述符链接到所生成的host代码,将其中的cubin对象作为全局初始化数据数组包含进来,但此时,kernel执行配置也要被转换为CUDA运行启动代码,以加载和启动编译后的kernel
  • 在调用CUDA驱动API时,CUDA运行时系统会查看这个描述符,根据当前的GPU load一块具有合适ISAimage,然后便可单独执行ptx代码或cubin对象,而忽略nvcc编译得到的host代码。
  • Nvcc的各个编译阶段以及行为是可以通过组合输入文件名和选项命令进行选择的。它是不区分输入文件类型的,如object, library or resource files,仅仅把当前要执行编译阶段需要的文件传递给linker

指定编译阶段

  • 主要指定编译的阶段是最基本的编译参数
  • -ccbin : 指定host编译器所在路径

简介

nvjpegBackend_t

  • 简述:
    • NVJPEG_BACKEND_DEFAULT – 默认值
    • NVJPEG_BACKEND_HYBRID – 使用CPU进行霍夫曼解码
    • NVJPEG_BACKEND_GPU_HYBRID – 使用GPU辅助霍夫曼解码。nvjpegDecodeBatched将使用GPU对基线JPEG比特流进行解码,当批量大小大于100时进行交错扫描
    • NVJPEG_BACKEND_HARDWARE – 单扫描支持基线JPEG比特流。不支持410和411个子样本
  • 声明:
    1
    2
    3
    4
    5
    6
    7
    typedef enum 
    {
    NVJPEG_BACKEND_DEFAULT = 0,
    NVJPEG_BACKEND_HYBRID = 1,
    NVJPEG_BACKEND_GPU_HYBRID = 2,
    NVJPEG_BACKEND_HARDWARE = 3
    } nvjpegBackend_t;

nvjpegDevAllocator_t

  • 简述:内存分配器使用提到的原型,提供给nvjpegCreateEx
    • 这个分配器将用于库中的所有设备内存分配
    • 无论如何,库都在进行智能分配(仅在需要时重新分配内存)
  • 声明:
    1
    2
    3
    4
    5
    typedef struct 
    {
    tDevMalloc dev_malloc;
    tDevFree dev_free;
    } nvjpegDevAllocator_t;

nvjpegImage_t

  • 简述:输出描述符。根据输出格式,被写入平面的数据
  • 声明:
    1
    2
    3
    4
    5
    typedef struct
    {
    unsigned char * channel[NVJPEG_MAX_COMPONENT];
    size_t pitch[NVJPEG_MAX_COMPONENT];
    } nvjpegImage_t;

英伟达API

nvjpegCreateSimple()

  • 简述:使用默认后端和默认内存分配器(cudaMalloc/cudaFree)初始化nvjpeg句柄
  • 声明:nvjpegStatus_t NVJPEGAPI nvjpegCreateSimple(nvjpegHandle_t *handle);
  • 参数:
    • handle – 编解码器实例,用于其他调用 – Input/Output
  • 返回值:
    • 成功 –
    • 失败 –

nvjpegCreate()

  • 简述:初始化nvjpeg句柄。此句柄用于所有连续调用
  • 声明:nvjpegStatus_t NVJPEGAPI nvjpegCreate(nvjpegBackend_t backend, nvjpegDevAllocator_t *dev_allocator, nvjpegHandle_t *handle);
  • 参数:
    • backend – 要使用的后端。目前支持默认或混合(目前相同)。 – Input
    • dev_allocator – 指向nvjpegDevAllocator的指针。如果为NULL - 使用默认cuda调用(cudaMalloc/cudaFree) – Input
    • handle – 编解码器实例,用于其他调用 – Input/Output
  • 返回值:
    • 成功 –
    • 失败 –

nvjpegDestroy()

  • 简述:释放句柄和资源
  • 声明:nvjpegStatus_t NVJPEGAPI nvjpegDestroy(nvjpegHandle_t handle);
  • 参数:
    • handle – 要释放的实例句柄 – Input/Output
  • 返回值:
    • 成功 –
    • 失败 –

nvjpegJpegStateCreate()

  • 简述:解码状态的初始化
  • 声明:nvjpegStatus_t NVJPEGAPI nvjpegJpegStateCreate(nvjpegHandle_t handle, nvjpegJpegState_t *jpeg_handle);
  • 参数:
    • handle – 编解码器实例 – Input
    • jpeg_handle – 解码的jpeg图像状态句柄 – Input/Output
  • 返回值:
    • 成功 –
    • 失败 –

nvjpegJpegStateDestroy()

  • 简述:释放jpeg图像句柄
  • 声明:nvjpegStatus_t NVJPEGAPI nvjpegJpegStateDestroy(nvjpegJpegState_t jpeg_handle);
  • 参数:
    • jpeg_handle – 解码的jpeg图像状态句柄 – Input/Output
  • 返回值:
    • 成功 –
    • 失败 –

nvjpegGetImageInfo()

  • 简述:检索图像信息,包括通道、每个图像层的宽度和高度,以及色度下采样。
    • 如果编码的通道小于NVJPEG_MAX_COMPONENT,那么零将被设置为缺失通道信息
    • 如果图像是三通道的,则所有三组都有效
    • This function is thread safe.
  • 声明:
    1
    2
    3
    4
    5
    6
    7
    8
    nvjpegStatus_t NVJPEGAPI nvjpegGetImageInfo(
    nvjpegHandle_t handle,
    const unsigned char *data,
    size_t length,
    int *nComponents,
    nvjpegChromaSubsampling_t *subsampling,
    int *widths,
    int *heights);
  • 参数:
    • handle – 编解码器实例 – Input
    • data – 指向缓冲区的指针,其中包含要解码的jpeg流数据。 – Input
    • length – jpeg图像缓冲区的长度。 – Input
    • nComponent – 图像的组件数(层数),目前只支持1通道(灰度)或3通道。 – Output
    • subsampling – 在这个JPEG中使用的色度下采样,参见nvjpegChromaSubsampling_t – Output
    • widths – 指向NVJPEG_MAX_COMPONENT的指针,返回每个通道的宽度。如果信道未编码,则为0 – Output
    • heights – 指向NVJPEG_MAX_COMPONENT的指针,返回每个通道的高度。如果信道未编码,则为0 – Output
  • 返回值:
    • 成功 –
    • 失败 –

nvjpegDecode()

  • 简述:解码单幅图像
    • API是后端不可知的。
    • 它将决定在内部使用哪个实现。目标缓冲区应该足够大,能够存储指定格式的输出。
    • 对于每个颜色平面,可以使用nvjpegGetImageInfo()检索图像大小,每个平面所需的最小内存缓冲区是nPlaneHeight*nPlanePitch
    • 其中nPlanePitch >= nPlaneWidth用于平面输出格式,
    • nPlanePitch >= nPlaneWidth*nOutputComponents用于交错输出格式
  • 声明:
    1
    2
    3
    4
    5
    6
    7
    8
    nvjpegStatus_t NVJPEGAPI nvjpegDecode(
    nvjpegHandle_t handle,
    nvjpegJpegState_t jpeg_handle,
    const unsigned char *data,
    size_t length,
    nvjpegOutputFormat_t output_format,
    nvjpegImage_t *destination,
    cudaStream_t stream);
  • 参数:
    • handle – 编解码器实例 – Input/Output
    • jpeg_handle – 解码的jpeg图像状态句柄 – Input/Output
    • data – 指向包含要解码的jpeg图像的缓冲区的指针。 – Input
    • length – jpeg图像缓冲区的长度。 – Input
    • output_format – 输出数据格式。有关描述,请参阅nvjpegOutputFormat_t – Input
    • destination – 指向结构的指针,包含输出缓冲区的信息。参见nvjpegImage_t描述。 – Input/Output
    • stream – CUDA流在哪里提交所有GPU工作 – Input/Output
  • 返回值:
    • 成功 –
    • 失败 –

cudaStreamSynchronize()

  • 简述:阻塞任务流,直到流完成所有操作。如果 device 设置了cudaDeviceScheduleBlockingSync标识,主机线程在它完成所有的任务之前,会一直阻塞
  • 声明:cudaError_t cudaStreamSynchronize(cudaStream_t stream);
  • 参数:
    • stream – 流标识符
  • 返回值:
    • 成功 – 返回cudaSuccess
    • 失败 – 返回cudaErrorInvalidResourceHandle

cudaStreamCreateWithFlags()

  • 简述:创建一个新的异步的流,通过参数flags来设置流的属性
    • Create an asynchronous stream Creates a new asynchronous stream.
    • The flags argument determines the behaviors of the stream.
  • 声明:cudaError_t cudaStreamCreateWithFlags(cudaStream_t *pStream, unsigned int flags);
  • 参数:
    • pStream – 指向流标识符的指针
    • flags – 创建流的参数,有效的参数为:
      • cudaStreamDefault – 默认标识
      • cudaStreamNonBlocking – 异步流,异步于NULL流
  • 返回值:
    • 成功 – cudaSuccess
    • 失败 – cudaErrorInvalidValue

cudaGetDeviceCount()

  • 简述:获取可用算能设备的数量
  • 声明:cudaError_t cudaGetDeviceCount(int *count);
  • 参数:
    • count – 具有计算能力的设备数字
  • 返回值:
    • 成功 – cudaSuccess

cudaGetDevice()

  • 简述:返回当前正在使用的设备
  • 声明:cudaError_t cudaGetDevice(int *device);
  • 参数:
    • device – 正在工作的主机线程所执行的设备代码
  • 返回值:
    • 成功 – cudaSuccess
    • 失败 – cudaErrorInvalidValue

cudaSetDevice()

  • 简述:设置GPU运行的设备
  • 声明:cudaError_t cudaSetDevice(int device);
  • 参数:
    • device – 调用这个函数的主机线程要执行设备代码的设备
  • 返回值:
    • 成功 – cudaSuccess
    • 失败 – cudaErrorInvalidDevice | cudaErrorDeviceAlreadyInUse
  • 注意:
    • 随后任何设备由cudaMalloc(), cudaMallocPitch(), cudaMallocArray()申请的设备内存都会分配到对应device的物理设备
    • 任何主机线程通过cudaMallocHost(), cudaHostAlloc(), cudaHostRegister()申请的主机内存都会将它们的生命周期与设备device关联起来
    • 任何通过主机线程创建的流或事件都会和设备device关联起来
    • 任何通过主机线程由cudaLaunchKernel()发生的内核调用都会在设备device上执行
    • 这个调用可能在任何时间,由任何主机线程在任何设备上调用。
  • 返回值:
    • 成功 – cudaSuccess
    • 失敗 – cudaErrorInvalidDevice | cudaErrorDeviceAlreadyInUse

简介

  • OpenCV 常见类,函数

OpenCV cv::flip() 函数 详解

cv::flip() 函数是 OpenCV 库中的一个函数,用于执行图像的翻转操作。该函数可以在水平、垂直或两个方向上翻转图像。下面是对 cv::flip() 函数的详解:

函数原型

1
2
3
4
5
void cv::flip(
InputArray src,
OutputArray dst,
int flipCode
)

参数说明

  • src:输入图像。
  • dst:输出图像,翻转后的结果将存储在这里。
  • flipCode:翻转操作的类型。可以是以下几种值之一:
    • 0:沿 x 轴翻转(垂直翻转)。
    • 1:沿 y 轴翻转(水平翻转)。
    • -1:同时沿 x 轴和 y 轴翻转。

功能

cv::flip() 函数用于对输入图像进行翻转操作。翻转操作可以是水平翻转、垂直翻转或同时在水平和垂直方向上进行翻转。翻转后的结果将存储在输出图像 dst 中。

注意事项

  • 输入图像和输出图像的尺寸和数据类型相同。
  • 如果需要在原地进行翻转操作(即输入图像和输出图像是同一个),可以将 srcdst 设置为相同的图像。

示例

1
2
3
4
5
6
cv::Mat src = cv::imread("input.jpg", cv::IMREAD_COLOR);
cv::Mat flippedImage;
cv::flip(src, flippedImage, 1); // 水平翻转

// 如果需要原地翻转,可以将 src 和 dst 设置为相同的图像
// cv::flip(src, src, 1);

在这个示例中,src 是一个输入的图像,通过 cv::flip() 函数将其进行水平翻转,翻转后的结果存储在 flippedImage 中。

cv::flip() 函数是 OpenCV 中用于执行图像翻转操作的常用函数之一,它在图像处理中广泛用于数据增强、镜像反射等应用场景。

OpenCV cv::findChessboardCorners() 函数 详解

cv::findChessboardCorners() 是 OpenCV(开源计算机视觉库)中的一个函数,用于在图像中找到棋盘格图案的内部角点。这个函数通常用于摄像机校准和计算机视觉应用,以确定棋盘格校准图案的角点位置,这对于摄像机校准非常重要。

以下是对 cv::findChessboardCorners() 函数的详细解释:

1
2
3
4
5
6
bool cv::findChessboardCorners(
InputArray image, // 包含棋盘格图案的输入图像。
Size patternSize, // 棋盘格图案的大小(内部角点的数量)。
OutputArray corners, // 存储找到的角点的输出向量。
int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE
)

参数:

  1. image:这是要在其中查找棋盘格角点的输入图像。通常应该是灰度图像,但也可以是彩色图像。

  2. patternSize:此参数指定了棋盘格图案的大小。它应该是一个 Size 对象,包含了棋盘格图案的行和列上的内部角点数量。

  3. corners:这是一个输出参数,函数会将找到的角点位置存储在其中。通常是一个包含2D点的向量(例如 std::vector<cv::Point2f>)。

  4. flags:可选参数,可用于指定各种操作标志。默认标志(CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE)适用于大多数情况。这些标志控制函数如何处理图像。

返回值:
如果成功找到棋盘格角点,则函数返回 true,否则返回 false

cv::findChessboardCorners() 的工作原理:

  1. 该函数分析输入图像以检测棋盘格图案的内部角点。

  2. 它使用自适应阈值技术对图像进行二值化并查找角点。

  3. 找到角点后,它将它们存储在corners 输出向量中。

  4. patternSize 参数帮助函数确定棋盘格图案的期望大小和布局,从而更容易找到角点。

用法:
通常,您将在摄像机校准过程的一部分中使用此函数,其中您从不同角度和距离捕获棋盘格图案的多幅图像。通过在这些图像中找到角点,您可以计算摄像机的内部和外部参数。

以下是如何使用 cv::findChessboardCorners() 的基本示例:

1
2
3
4
5
6
7
8
9
cv::Mat image = cv::imread("chessboard.png", cv::IMREAD_GRAYSCALE);
cv::Size patternSize(7, 7); // 指定棋盘格图案的大小。
std::vector<cv::Point2f> corners;
bool found = cv::findChessboardCorners(image, patternSize, corners);
if (found) {
// 找到了角点,可以继续进行摄像机校准。
} else {
// 未在图像中找到角点。
}

这个函数是摄像机校准的关键步骤,通常用于需要准确了解摄像机参数的计算机视觉应用中。

C++ OpenCV cv::ellipse() 函数 详解

cv::ellipse() 函数是OpenCV中用于绘制椭圆的函数。它允许您在图像上绘制椭圆,可以用于标记或可视化图像中的对象或区域。以下是cv::ellipse()函数的详解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void cv::ellipse(
InputOutputArray img, // 输出图像
Point center, // 椭圆的中心坐标
Size axes, // 长轴和短轴的大小
double angle, // 旋转角度(以度为单位)
double startAngle, // 开始角度(以度为单位)
double endAngle, // 结束角度(以度为单位)
const Scalar& color, // 椭圆的颜色(BGR格式)
int thickness = 1, // 边框线宽
int lineType = LINE_8, // 线的类型
int shift = 0 // 点坐标的小数位数
);`cv::findChessboardCorners()` 是 OpenCV 中用于检测棋盘格角点的函数。这个函数主要用于摄像机标定和校正。下面是有关 `cv::findChessboardCorners()` 函数的详细解释:

```cpp
bool cv::findChessboardCorners(const cv::Mat& image, cv::Size patternSize, cv::OutputArray corners, int flags = cv::CALIB_CB_ADAPTIVE_THRESH + cv::CALIB_CB_NORMALIZE_IMAGE)

参数解释:

  • image:输入的图像,通常是拍摄的包含了棋盘格的图像。
  • patternSize:一个 cv::Size 对象,指定了棋盘格的内部角点的行和列数,通常为 (num_cols, num_rows)
  • corners:用于输出检测到的角点坐标的 cv::OutputArray。这是一个包含检测到的角点的 2D 坐标的向量。
  • flags:可选参数,用于指定不同的标志位以控制检测的方式。常用的标志包括:
    • cv::CALIB_CB_ADAPTIVE_THRESH:使用自适应阈值方法。
    • cv::CALIB_CB_NORMALIZE_IMAGE:在角点检测之前归一化图像。
    • cv::CALIB_CB_FAST_CHECK:执行快速检查以排除不合格的图像。

返回值:

  • 如果成功检测到棋盘格角点,函数返回 true,否则返回 false

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cv::Mat image = cv::imread("chessboard.jpg");
cv::Size patternSize(7, 6); // 棋盘格的列数和行数
cv::Mat grayImage;
cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY);

std::vector<cv::Point2f> corners;
bool found = cv::findChessboardCorners(grayImage, patternSize, corners);

if (found) {
// 在图像上绘制角点
cv::drawChessboardCorners(image, patternSize, cv::Mat(corners), found);
cv::imshow("Chessboard Corners", image);
cv::waitKey(0);
}

cv::findChessboardCorners() 函数的主要用途是用于相机标定,以便校正图像畸变。检测到的角点可以用于计算相机的内部和外部参数,以及图像的校正和畸变校正。通常,使用棋盘格图像进行标定是摄影测量和计算机视觉中的一个重要步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

参数说明:

- `img`:输入输出图像,您可以在其上绘制椭圆。

- `center`:椭圆的中心坐标,通常表示为`cv::Point`对象。

- `axes`:一个`cv::Size`对象,表示椭圆的长轴和短轴的大小。

- `angle`:椭圆的旋转角度,以度为单位。正值表示顺时针旋转,负值表示逆时针旋转。

- `startAngle`:起始角度,以度为单位。椭圆将从这个角度开始绘制。

- `endAngle`:结束角度,以度为单位。椭圆将绘制到这个角度。

- `color`:椭圆的颜色,通常表示为`cv::Scalar`对象,使用BGR颜色格式。

- `thickness`:椭圆的边框线宽,默认为1。

- `lineType`:线的类型,可以是`LINE_4`、`LINE_8`或`LINE_AA`中的一个,分别表示4-连通、8-连通和抗锯齿线。

- `shift`:点坐标的小数位数,通常为0。

示例用法:

```cpp
#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
Mat image(400, 400, CV_8UC3, Scalar(255, 255, 255)); // 创建白色背景图像

Point center(200, 200);
Size axes(100, 50);
double angle = 30;
double startAngle = 0;
double endAngle = 360;
Scalar color(0, 0, 255); // 蓝色

ellipse(image, center, axes, angle, startAngle, endAngle, color, 2);

imshow("Ellipse", image);
waitKey(0);

return 0;
}

上述示例创建了一个400x400的白色背景图像,在其中绘制了一个蓝色的椭圆。您可以根据需要调整参数以满足您的绘制需求。

Mat operator()( const Rect& roi ) const 详解

cv::Mat 类中的 operator()(const Rect& roi) const 是一个用于提取感兴趣区域(Region of Interest,ROI)的运算符重载。它允许你根据给定的矩形区域来创建一个新的 cv::Mat 对象,其中包含了原始图像中该矩形区域的像素数据。以下是这个运算符重载的详细解释:

1
cv::Mat cv::Mat::operator()(const cv::Rect& roi) const;

参数说明:

  • roi:一个 cv::Rect 对象,表示感兴趣区域的位置和大小。

运算符重载的返回值是一个新的 cv::Mat 对象,其中包含了原始图像中指定感兴趣区域的像素数据。

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <opencv2/opencv.hpp>

int main() {
// 读取图像
cv::Mat image = cv::imread("image.jpg");

// 定义感兴趣区域的矩形
cv::Rect roiRect(100, 100, 200, 150); // (x, y, width, height)

// 使用运算符重载提取感兴趣区域
cv::Mat roi = image(roiRect);

// 显示感兴趣区域
cv::imshow("感兴趣区域", roi);
cv::waitKey(0);

return 0;
}

在上述示例中,我们首先读取了一张图像,然后定义了一个 cv::Rect 对象 roiRect,表示感兴趣区域的位置和大小。接着,我们使用运算符重载 image(roiRect) 来提取图像中指定的感兴趣区域,并将其存储在 roi 中。最后,我们显示了这个感兴趣区域。这个运算符重载使得提取感兴趣区域变得非常方便,可以快速获取图像的子区域进行进一步处理。

cv::Mat::setTo() 函数 详解

cv::Mat::setTo() 函数用于将图像中的所有像素设置为指定的值或颜色。这个函数非常有用,可以用来创建具有特定像素值的图像,或者将图像的所有像素设置为某个常数值。以下是 cv::Mat::setTo() 函数的详解:

1
void cv::Mat::setTo(const Scalar& value, const Mat& mask = Mat());

参数说明:

  • value:要设置的像素值,通常是一个 cv::Scalar 对象,表示图像的颜色或强度。例如,如果你想将图像设置为纯黑色,可以使用 cv::Scalar(0, 0, 0)
  • mask:可选参数,一个与输入图像相同大小的掩码图像,用于指定哪些像素应该被设置为指定的值。只有在掩码图像中对应的像素值为非零时,对应的目标图像像素才会被设置。默认情况下,没有掩码。

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <opencv2/opencv.hpp>

int main() {
// 创建一个空的图像
cv::Mat image(300, 400, CV_8UC3); // 3通道,8位无符号整数类型

// 设置整个图像为纯蓝色
cv::Scalar blueColor(255, 0, 0);
image.setTo(blueColor);

// 创建一个掩码图像,只有图像中心部分的像素会被修改
cv::Mat mask = cv::Mat::zeros(image.size(), CV_8UC1);
cv::Rect roiRect(100, 100, 200, 100);
mask(roiRect) = 255; // 将中心区域的掩码设置为255

// 使用掩码将中心部分设置为绿色
cv::Scalar greenColor(0, 255, 0);
image.setTo(greenColor, mask);

// 显示图像
cv::imshow("图像", image);
cv::waitKey(0);

return 0;
}

在上述示例中,我们首先创建了一个空的彩色图像,然后使用 setTo() 函数将整个图像设置为纯蓝色。接下来,我们创建了一个掩码图像,只有中心区域的像素才会被修改。最后,我们使用掩码将中心部分的像素设置为绿色。这是一个简单示例,演示了如何使用 cv::Mat::setTo() 函数来设置图像的像素值。

cv::resize() 函数 详解

cv::resize() 函数用于调整图像或图像区域的大小。你可以使用它来缩小或放大图像,或者将图像裁剪到指定的尺寸。以下是 cv::resize() 函数的详解:

1
2
3
4
5
6
7
8
void cv::resize(
InputArray src, // 输入图像,可以是 cv::Mat 或其他图像数据结构
OutputArray dst, // 输出图像,可以是 cv::Mat 或其他图像数据结构
Size dsize, // 目标图像的大小,指定为 cv::Size(width, height)
double fx = 0, // 沿水平轴的缩放因子
double fy = 0, // 沿垂直轴的缩放因子
int interpolation = INTER_LINEAR // 插值方法,可选,默认为线性插值
);

参数说明:

  • src:输入图像,可以是 cv::Mat 或其他支持的图像数据结构。
  • dst:输出图像,可以是 cv::Mat 或其他支持的图像数据结构。它将包含调整大小后的图像。
  • dsize:目标图像的大小,以 cv::Size 对象表示,指定为目标宽度和高度。
  • fx:沿水平轴的缩放因子。如果为0,则根据垂直缩放因子 fy 来确定输出大小。
  • fy:沿垂直轴的缩放因子。如果为0,则根据水平缩放因子 fx 来确定输出大小。
  • interpolation:插值方法,用于在调整大小时估算像素值。可以选择以下插值方法之一:
    • INTER_NEAREST:最近邻插值(速度最快,但质量较差)
    • INTER_LINEAR:双线性插值(默认,速度适中,质量较好)
    • INTER_CUBIC:双三次插值(速度较慢,质量较好)
    • INTER_LANCZOS4:Lanczos插值(速度较慢,质量最好)

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <opencv2/opencv.hpp>

int main() {
// 读取图像
cv::Mat image = cv::imread("image.jpg");

// 指定目标大小
cv::Size targetSize(200, 150);

// 调整图像大小
cv::Mat resizedImage;
cv::resize(image, resizedImage, targetSize, 0, 0, cv::INTER_LINEAR);

// 显示原始图像和调整大小后的图像
cv::imshow("原始图像", image);
cv::imshow("调整大小后的图像", resizedImage);
cv::waitKey(0);

return 0;
}

上述示例将读取一张图像,将其调整为指定的目标大小,然后显示原始图像和调整大小后的图像。你可以根据需要选择不同的插值方法和目标大小。

cv::convexHull

在 OpenCV 中,cv::convexHull() 函数用于计算一个给定点集的凸包。凸包是包含给定点集中所有点的最小凸多边形。以下是关于 cv::convexHull() 函数的详细解释:

函数签名

1
cv::convexHull(InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true)

参数

  • points:输入的点集,可以是 cv::Matcv::Mat_<T>cv::vector<Point> 等。
  • hull:计算出的凸包点集。
  • clockwise:指定计算的凸包是否按逆时针方向排列。
  • returnPoints:如果为 true,函数将返回凸包的点集;如果为 false,函数将返回凸包的索引。

返回值

  • 函数没有返回值。凸包的点集或索引存储在 hull 参数中。

示例
以下示例演示了如何使用 cv::convexHull() 函数计算一组点的凸包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <opencv2/opencv.hpp>

using namespace cv;

int main() {
std::vector<Point2f> points;
points.push_back(Point2f(100, 100));
points.push_back(Point2f(300, 100));
points.push_back(Point2f(300, 200));
points.push_back(Point2f(100, 200));

std::vector<Point2f> hull;
convexHull(points, hull);

Mat image = Mat::zeros(300, 400, CV_8UC3);
for (const Point2f& p : points) {
circle(image, p, 5, Scalar(0, 0, 255), -1); // 绘制红色点
}

for (size_t i = 0; i < hull.size(); i++) {
line(image, hull[i], hull[(i + 1) % hull.size()], Scalar(255, 0, 0), 2); // 绘制蓝色凸包边界
}

imshow("Convex Hull", image);
waitKey(0);

return 0;
}

在上述示例中,我们创建了一个点集 points,并使用 convexHull() 函数计算了这些点的凸包。然后,我们使用绘图函数绘制了原始点和凸包的边界。

运行示例代码后,你将在窗口中看到原始点和计算出的凸包。你可以根据需要调整点的坐标,了解函数的不同参数和用法,以适应你的实际需求。

cv::RotatedRect::points() 函数 详解

cv::RotatedRect::points() 是 OpenCV 中 cv::RotatedRect 类的一个成员函数,用于获取旋转矩形的四个顶点坐标。以下是对 cv::RotatedRect::points() 函数的详细解释:

函数原型:

1
void cv::RotatedRect::points(cv::Point2f pts[]) const

函数功能:
cv::RotatedRect::points() 函数用于获取一个旋转矩形的四个顶点坐标,并将这些坐标存储在提供的数组 pts 中。旋转矩形是由中心点、宽度、高度和旋转角度定义的,它可以是在任意角度下旋转的矩形。

参数:

  • pts:一个大小为4的 cv::Point2f 数组,用于存储旋转矩形的四个顶点坐标。数组应该在调用函数之前分配好空间。

返回值:
函数没有返回值,它直接将四个顶点的坐标存储在提供的数组 pts 中。

示例用法:
以下是一个使用 cv::RotatedRect::points() 函数的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <opencv2/opencv.hpp>

int main() {
cv::Point2f points[4]; // 存储顶点坐标的数组
cv::RotatedRect rotatedRect(cv::Point2f(100, 100), cv::Size2f(200, 100), 30); // 创建一个旋转矩形

rotatedRect.points(points); // 获取旋转矩形的顶点坐标

for (int i = 0; i < 4; ++i) {
std::cout << "Point " << i + 1 << ": (" << points[i].x << ", " << points[i].y << ")" << std::endl;
}

return 0;
}

在上面的示例中,我们首先创建了一个 cv::RotatedRect 对象,然后使用 cv::RotatedRect::points() 函数获取了该旋转矩形的四个顶点坐标。最后,我们遍历数组 points,输出每个顶点的坐标。

总之,cv::RotatedRect::points() 函数是一个方便的函数,用于获取旋转矩形的四个顶点坐标,这对于后续的绘制和分析操作非常有用。

cv::Size2i::area() 函数 详解

cv::Size2i是OpenCV中用于表示二维大小的类,其中cv::Size2i::area()是一个成员函数,用于计算这个二维大小的面积。以下是对cv::Size2i::area()函数的详细解释:

函数原型:

1
int cv::Size2i::area() const

函数功能:
cv::Size2i::area()函数用于计算表示二维大小的cv::Size2i对象的面积。对于cv::Size2i对象,面积等于其宽度(width)乘以高度(height)。

参数:
该函数没有参数,因为它是一个成员函数,作用于cv::Size2i对象本身。

返回值:
函数返回一个整数,表示cv::Size2i对象的面积。

示例用法:
下面是一个使用cv::Size2i::area()函数的示例代码:

1
2
3
4
5
6
7
8
9
10
11
#include <opencv2/opencv.hpp>

int main() {
cv::Size2i size(5, 8); // 创建一个二维大小为(5, 8)的对象
int area = size.area(); // 计算面积,area = 5 * 8 = 40

std::cout << "Width: " << size.width << ", Height: " << size.height << std::endl;
std::cout << "Area: " << area << std::endl;

return 0;
}

在上面的示例中,我们创建了一个cv::Size2i对象,然后使用cv::Size2i::area()函数计算了其面积。面积的计算结果是对象的宽度和高度的乘积。

总而言之,cv::Size2i::area()函数是OpenCV中用于计算cv::Size2i对象面积的方便函数,它直接返回对象宽度和高度的乘积。

cv::fillPoly() 函数 详解

cv::fillPoly() 函数是 OpenCV 库中的一个函数,用于在图像上填充指定的多边形区域。

以下是关于 cv::fillPoly() 函数的详细解释:

  • 函数签名:函数的签名如下:

    1
    2
    3
    4
    5
    6
    7
    void cv::fillPoly(
    InputOutputArray img,
    InputArrayOfArrays pts,
    const Scalar &color,
    int lineType = LINE_8,
    int shift = 0,
    Point offset = Point());
  • 参数

    • img:要填充的图像。
    • pts:多边形的顶点集合,可以是一个包含 cv::Pointstd::vector,也可以是 cv::Mat
    • color:填充的颜色,类型为 cv::Scalar,表示 BGR 通道的颜色值。
    • lineType:线段的类型,默认为 LINE_8,表示8连通线段。
    • shift:坐标的小数部分位数,默认为0。
    • offset:坐标的偏移,默认为 Point()
  • 功能

    • 该函数用于在图像上填充一个或多个多边形区域,根据给定的多边形顶点集合和颜色进行填充。
    • 填充的多边形可以是简单多边形,也可以是包含洞的多边形。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    cv::Mat image(300, 300, CV_8UC3, cv::Scalar(0, 0, 0));

    std::vector<cv::Point> polygon;
    polygon.push_back(cv::Point(50, 50));
    polygon.push_back(cv::Point(150, 50));
    polygon.push_back(cv::Point(150, 150));
    polygon.push_back(cv::Point(50, 150));

    std::vector<std::vector<cv::Point>> polygons;
    polygons.push_back(polygon);

    cv::fillPoly(image, polygons, cv::Scalar(0, 255, 0));

    cv::imshow("Filled Image", image);
    cv::waitKey(0);
  • 注意事项

    • pts 参数接受一个包含多个多边形的顶点集合,因此你可以一次性填充多个多边形。
    • 该函数会直接在输入图像上进行填充操作,所以确保图像的尺寸和类型与所需操作匹配。

总之,cv::fillPoly() 函数是用于在图像上填充多边形区域的函数。通过提供多边形的顶点集合和填充颜色,你可以在图像中创建各种填充区域,用于可视化和图像处理。

cv::Mat::ptr() 函数 详解

cv::Mat::ptr() 函数是 OpenCV 库中 cv::Mat 类的一个成员函数,用于获取指定行的指针,从而允许对图像或矩阵数据进行低级别的访问和操作。

以下是关于 cv::Mat::ptr() 函数的详细解释:

  • 函数签名:函数的签名如下:

    1
    uchar* cv::Mat::ptr(int i = 0);
  • 参数

    • i:指定要获取指针的行数(默认为0),即行的索引。行索引从0开始。
  • 返回值

    • 返回一个 uchar* 类型的指针,指向指定行的数据。这个指针可以用于低级别的数据访问操作。
  • 功能

    • 该函数用于获取指定行的指针,允许你对图像或矩阵数据进行低级别的访问和操作。通过获取指针,你可以在不使用高级 OpenCV 函数的情况下,直接读取和修改像素值。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    cv::Mat image = cv::imread("image.jpg", cv::IMREAD_COLOR);

    if (!image.empty()) {
    int row = 100;

    uchar* ptr = image.ptr(row);

    for (int col = 0; col < image.cols; ++col) {
    uchar blue = ptr[col * 3]; // Blue channel
    uchar green = ptr[col * 3 + 1]; // Green channel
    uchar red = ptr[col * 3 + 2]; // Red channel
    }
    }
  • 注意事项

    • 通过 cv::Mat::ptr() 获取的指针可以用于访问指定行的像素数据。如果你需要访问其他通道的像素值,你需要根据通道数进行适当的偏移。
    • 注意确保访问的像素在图像范围内,以避免越界访问。

总之,cv::Mat::ptr() 函数是用于获取指定行的指针,从而允许你对图像或矩阵数据进行低级别的访问和操作。这对于一些特定的图像处理任务可能会很有用,但需要小心越界访问。

cv::Mat::ptr() 函数 详解

cv::Mat::ptr<uint16_t>() 函数是 OpenCV 库中 cv::Mat 类的一个成员函数的模板化版本,用于获取指定行的指针,并将指针的数据类型设置为 uint16_t,从而允许对图像或矩阵数据进行低级别的访问和操作,特别适用于 uint16_t 类型的像素数据。

以下是关于 cv::Mat::ptr<uint16_t>() 函数的详细解释:

  • 函数签名:函数的签名如下:

    1
    uint16_t* cv::Mat::ptr<uint16_t>(int i = 0);
  • 参数

    • i:指定要获取指针的行数(默认为0),即行的索引。行索引从0开始。
  • 返回值

    • 返回一个 uint16_t* 类型的指针,指向指定行的数据。这个指针可以用于低级别的数据访问操作。
  • 功能

    • 该函数用于获取指定行的指针,并将指针的数据类型设置为 uint16_t,允许你对图像或矩阵数据进行低级别的访问和操作。通过获取指针,你可以在不使用高级 OpenCV 函数的情况下,直接读取和修改像素值。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    cv::Mat depth_image = cv::imread("depth_image.png", cv::IMREAD_ANYDEPTH);

    if (!depth_image.empty() && depth_image.depth() == CV_16U) {
    int row = 100;

    uint16_t* ptr = depth_image.ptr<uint16_t>(row);

    for (int col = 0; col < depth_image.cols; ++col) {
    uint16_t depth_value = ptr[col];
    }
    }
  • 注意事项

    • 通过 cv::Mat::ptr<uint16_t>() 获取的指针可以用于访问指定行的像素数据,数据类型为 uint16_t。确保图像的深度为 CV_16U,以匹配数据类型。
    • 注意确保访问的像素在图像范围内,以避免越界访问。

总之,cv::Mat::ptr<uint16_t>() 函数是用于获取指定行的指针,并将数据类型设置为 uint16_t,允许你对图像或矩阵数据进行低级别的访问和操作。这对于访问 uint16_t 类型的像素数据非常有用,但需要小心越界访问。

cv::pointPolygonTest() 函数 详解

cv::pointPolygonTest() 函数是 OpenCV 库中的一个函数,用于计算一个点与一个多边形之间的关系,即点相对于多边形的内部、外部还是在多边形的边界上。

以下是关于 cv::pointPolygonTest() 函数的详细解释:

  • 函数签名:函数的签名如下:

    1
    2
    3
    4
    double cv::pointPolygonTest(
    InputArray contour,
    Point2f pt,
    bool measureDist);
  • 参数

    • contour:表示多边形的轮廓的输入数组。可以是一个 std::vector<cv::Point>,也可以是一个 cv::Mat
    • pt:要测试的点的坐标,类型为 cv::Point2f
    • measureDist:一个布尔值,表示是否要计算点到多边形边界的距离。如果为 true,函数将返回点到多边形的距离;如果为 false,函数将只返回点相对于多边形的关系(内部、外部或边界)。
  • 返回值

    • 如果 measureDist 参数为 false,返回值的含义如下:
      • 正数:点在多边形内部。
      • 零:点在多边形的边界上。
      • 负数:点在多边形外部。
    • 如果 measureDist 参数为 true,返回值表示点到多边形边界的有符号距离。距离为正数表示点在多边形内部,为零表示点在多边形边界上,为负数表示点在多边形外部。
  • 功能

    • 该函数用于计算一个点与一个多边形之间的关系。它可以确定点相对于多边形的位置,以及点到多边形边界的距离(可选)。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    std::vector<cv::Point> contour = {cv::Point(50, 50), cv::Point(200, 50), cv::Point(200, 200), cv::Point(50, 200)};
    cv::Point2f point(100, 100);

    double distance = cv::pointPolygonTest(contour, point, true);

    if (distance > 0) {
    std::cout << "Point is inside the polygon." << std::endl;
    } else if (distance == 0) {
    std::cout << "Point is on the polygon boundary." << std::endl;
    } else {
    std::cout << "Point is outside the polygon." << std::endl;
    }
  • 注意事项

    • 如果你只关心点相对于多边形的关系而不需要距离信息,可以将 measureDist 参数设置为 false,以提高性能。
    • 多边形的轮廓必须是闭合的,并且点的坐标应在 floatdouble 类型的范围内。

总之,cv::pointPolygonTest() 函数是用于计算点与多边形之间关系的函数,可以判断点是否在多边形内部、外部或边界上,并可以计算点到多边形边界的距离。

cv::Mat::atcv::Vec3b() 函数 详解

cv::Mat::at<cv::Vec3b>() 函数是 OpenCV 库中 cv::Mat 类的一个成员函数,用于访问图像或矩阵中特定位置的像素值。在这个函数中,cv::Vec3b 是一个表示三通道(BGR 或 RGB)像素值的数据类型。

以下是关于 cv::Mat::at<cv::Vec3b>() 函数的详细解释:

  • 函数签名:函数的签名如下:

    1
    cv::Vec3b& cv::Mat::at<cv::Vec3b>(int row, int col);
  • 参数

    • row:像素所在的行数(y 坐标)。
    • col:像素所在的列数(x 坐标)。
  • 返回值

    • 返回一个引用(cv::Vec3b&)到指定位置的像素值。cv::Vec3b 表示一个三通道的像素,例如 (B, G, R)(R, G, B)
  • 功能

    • 该函数用于访问图像或矩阵中指定位置的像素值。返回的引用允许你读取或修改像素的通道值。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    cv::Mat image = cv::imread("image.jpg");

    if (!image.empty()) {
    int row = 100;
    int col = 150;

    cv::Vec3b pixel = image.at<cv::Vec3b>(row, col);

    uchar blue = pixel[0];
    uchar green = pixel[1];
    uchar red = pixel[2];

    std::cout << "Pixel at (" << col << ", " << row << "): "
    << "B: " << int(blue) << " G: " << int(green) << " R: " << int(red) << std::endl;
    }
  • 注意事项

    • cv::Vec3b 表示三通道像素值,它的索引是 [0][1][2] 分别表示蓝色、绿色和红色通道。但需要注意,OpenCV 中的图像默认使用 BGR 顺序。

总之,cv::Mat::at<cv::Vec3b>() 函数是用于访问图像或矩阵中指定位置的三通道像素值的函数。通过该函数,你可以方便地读取和修改图像的像素值,并进行各种图像处理操作。

cv::circle

在 OpenCV 中,cv::circle 是一个用于在图像上绘制圆的函数。这个函数可以用来绘制一个圆,或者在图像上绘制多个圆。

函数原型如下:

1
void cv::circle(cv::InputOutputArray img, cv::Point center, int radius, const cv::Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);

函数参数解释:

  • img: 输入/输出图像,可以是单通道或多通道图像。
  • center: cv::Point 类型的参数,表示圆心的坐标。
  • radius: 圆的半径。
  • color: 绘制圆的颜色,可以使用 cv::Scalar 类来表示颜色。
  • thickness: 圆边界的宽度(线条粗细),默认值为 1。
  • lineType: 圆边界的类型,可以是 LINE_8(8 连通线)或 LINE_AA(反锯齿线)。
  • shift: 坐标值的小数位数,通常为 0。

下面是一个示例代码,演示了如何使用 cv::circle 函数绘制圆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <opencv2/opencv.hpp>

int main() {
// 创建一个黑色图像
cv::Mat image(300, 400, CV_8UC3, cv::Scalar(0, 0, 0));

// 定义圆心坐标和半径
cv::Point center(200, 150);
int radius = 50;

// 绘制圆
cv::circle(image, center, radius, cv::Scalar(0, 0, 255), 2);

// 显示绘制的图像
cv::imshow("Circle Example", image);
cv::waitKey(0);

return 0;
}

在这个示例中,我们创建了一个黑色的图像,然后使用 cv::circle 函数绘制了一个红色的圆,圆心为 (200, 150),半径为 50。

cv::circle 函数在图像绘制中非常有用,可以用于标记、可视化、边缘检测结果显示等多种情况。可以通过调整参数来控制圆的颜色、半径和边界样式。


cv::Rect

在 OpenCV 中,cv::Rect 是一个用于表示矩形的类。它通常用于图像处理和计算机视觉中,用于表示图像中的感兴趣区域(ROI),或者用于描述物体的边界框。

cv::Rect 的定义位于 opencv2/core.hpp 头文件中,其结构如下:

1
2
3
4
5
6
7
8
9
class Rect {
public:
Rect();
Rect(int x, int y, int width, int height);
Rect(const Rect& r);

int x, y; // 矩形左上角的坐标
int width, height; // 矩形的宽度和高度
};

这个类有以下几个构造函数和成员变量:

  • Rect(): 默认构造函数,会将矩形的属性初始化为默认值。
  • Rect(int x, int y, int width, int height): 构造函数,传入矩形的左上角坐标、宽度和高度。
  • Rect(const Rect& r): 复制构造函数,用于创建一个已有矩形的副本。
  • xy: 整数类型的成员变量,表示矩形左上角的 x 和 y 坐标。
  • widthheight: 整数类型的成员变量,表示矩形的宽度和高度。

下面是一个示例代码,演示了如何使用 cv::Rect 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <opencv2/opencv.hpp>

int main() {
// 创建一个矩形对象
cv::Rect rect1(100, 50, 200, 150);

// 访问矩形的属性
int x = rect1.x;
int y = rect1.y;
int width = rect1.width;
int height = rect1.height;

// 输出矩形的属性
std::cout << "Rectangle: x=" << x << ", y=" << y << ", width=" << width << ", height=" << height << std::endl;

// 创建另一个矩形对象
cv::Rect rect2(50, 30, 100, 80);

// 使用复制构造函数创建新矩形对象
cv::Rect rect3 = rect2;

return 0;
}

cv::Rect 类在许多图像处理和计算机视觉任务中非常有用,特别是在需要表示感兴趣区域、物体的边界框等情况。它可以帮助您存储和操作矩形的属性,以及在图像处理中进行相关操作,例如提取感兴趣区域、绘制边界框等。


cv::line

在 OpenCV 中,cv::line 是一个用于在图像上绘制直线的函数。这个函数可以用来绘制一条连接两个点的直线,或者在图像上绘制多条直线。

函数原型如下:

1
void cv::line(cv::InputOutputArray img, cv::Point pt1, cv::Point pt2, const cv::Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);

函数参数解释:

  • img: 输入/输出图像,可以是单通道或多通道图像。
  • pt1pt2: 两个 cv::Point 类型的参数,表示直线的起点和终点。
  • color: 绘制直线的颜色,可以使用 cv::Scalar 类来表示颜色。
  • thickness: 直线的宽度(线条粗细),默认值为 1。
  • lineType: 直线的类型,可以是 LINE_8(8 连通线)或 LINE_AA(反锯齿直线)。
  • shift: 坐标值的小数位数,通常为 0。

下面是一个示例代码,演示了如何使用 cv::line 函数绘制直线:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <opencv2/opencv.hpp>

int main() {
// 创建一个黑色图像
cv::Mat image(300, 400, CV_8UC3, cv::Scalar(0, 0, 0));

// 定义起点和终点坐标
cv::Point pt1(50, 100);
cv::Point pt2(350, 200);

// 绘制直线
cv::line(image, pt1, pt2, cv::Scalar(0, 0, 255), 2);

// 显示绘制的图像
cv::imshow("Line Example", image);
cv::waitKey(0);

return 0;
}

在这个示例中,我们创建了一个黑色的图像,然后使用 cv::line 函数在图像上绘制了一条红色的直线,连接了起点 (50, 100) 和终点 (350, 200)

cv::line 函数在图像绘制中非常有用,可以用于标记、可视化、边缘检测结果显示等多种情况。可以通过调整参数来控制直线的颜色、宽度和类型。


cv::Size

在 OpenCV 中,cv::Size 是一个用于表示大小(尺寸)的类。它通常用于图像处理和计算机视觉中,用于表示图像的宽度和高度,或者表示其他物体、区域的尺寸。

cv::Size 的定义位于 opencv2/core.hpp 头文件中,其结构如下:

1
2
3
4
5
6
7
8
class Size {
public:
Size();
Size(int width, int height);
Size(const Size& sz);

int width, height;
};

这个类有以下几个构造函数和成员变量:

  • Size(): 默认构造函数,会将宽度和高度初始化为 0。
  • Size(int width, int height): 构造函数,传入宽度和高度。
  • Size(const Size& sz): 复制构造函数,用于创建一个已有尺寸的副本。
  • widthheight: 整数类型的成员变量,分别表示尺寸的宽度和高度。

下面是一个示例代码,演示了如何使用 cv::Size 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <opencv2/opencv.hpp>

int main() {
// 创建一个尺寸对象
cv::Size size1(640, 480);

// 访问尺寸的宽度和高度
int width = size1.width;
int height = size1.height;

// 输出尺寸的宽度和高度
std::cout << "Size: " << width << " x " << height << std::endl;

// 创建另一个尺寸对象
cv::Size size2(320, 240);

// 使用复制构造函数创建新尺寸对象
cv::Size size3 = size2;

return 0;
}

cv::Size 类在许多图像处理和计算机视觉任务中非常有用,特别是在需要表示图像的大小、物体的尺寸等情况。它可以帮助您存储和操作尺寸信息,以及在图像处理中进行相关操作,例如调整图像大小、计算物体的尺寸等。


cv::Point2f

在 OpenCV 中,cv::Point2f 是一个用于表示二维浮点坐标点的类。它是 cv::Point 的变体,其中坐标值使用浮点数而不是整数表示。cv::Point2f 类通常用于图像处理和计算机视觉中,特别是在需要使用浮点坐标来表示像素位置、图像上的点、图像中的特征点等情况。

cv::Point2f 的定义位于 opencv2/core/types.hpp 头文件中,其结构如下:

1
2
3
4
5
6
7
8
class Point2f {
public:
Point2f();
Point2f(float x, float y);
Point2f(const Point2f& pt);

float x, y;
};

这个类有以下几个构造函数和成员变量:

  • Point2f(): 默认构造函数,会将坐标点初始化为 (0, 0)。
  • Point2f(float x, float y): 构造函数,传入 x 和 y 坐标值。
  • Point2f(const Point2f& pt): 复制构造函数,用于创建一个已有点的副本。
  • xy: 浮点数类型的成员变量,表示点的 x 和 y 坐标。

下面是一个示例代码,演示了如何使用 cv::Point2f 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <opencv2/opencv.hpp>

int main() {
// 创建一个点对象
cv::Point2f pt1(10.5, 20.3);

// 访问点的坐标
float x = pt1.x;
float y = pt1.y;

// 输出点的坐标
std::cout << "Point coordinates: (" << x << ", " << y << ")" << std::endl;

// 创建另一个点对象
cv::Point2f pt2(5.2, 8.7);

// 使用复制构造函数创建新点对象
cv::Point2f pt3 = pt2;

return 0;
}

cv::Point2f 类在许多情况下都非常有用,特别是当需要使用浮点坐标来精确表示像素位置、特征点位置等时。它是 OpenCV 中基本的数据结构之一,用于处理图像中的点和坐标。


cv::RotatedRect

在 OpenCV 中,cv::RotatedRect 是一个用于表示旋转矩形的类。旋转矩形是一个带有角度的矩形,通常用于表示在图像中检测到的旋转物体的边界框。

cv::RotatedRect 的定义位于 opencv2/core/types.hpp 头文件中,其结构如下:

1
2
3
4
5
6
7
8
9
class RotatedRect {
public:
RotatedRect();
RotatedRect(const Point2f& center, const Size2f& size, float angle);

Point2f center; // 旋转矩形中心坐标
Size2f size; // 旋转矩形的尺寸,宽度和高度
float angle; // 旋转矩形的角度(以度为单位)
};

这个类有以下几个构造函数和成员变量:

  • RotatedRect(): 默认构造函数,会将旋转矩形的属性初始化为默认值。
  • RotatedRect(const Point2f& center, const Size2f& size, float angle): 构造函数,传入旋转矩形的中心坐标、尺寸和角度。

成员变量解释:

  • center: 一个 Point2f 类型的成员变量,表示旋转矩形的中心坐标。
  • size: 一个 Size2f 类型的成员变量,表示旋转矩形的宽度和高度。
  • angle: 一个浮点数,表示旋转矩形的角度,单位为度。

下面是一个示例代码,演示了如何使用 cv::RotatedRect 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <opencv2/opencv.hpp>

int main() {
cv::Point2f center(100, 100);
cv::Size2f size(200, 100);
float angle = 30.0;

// 创建一个旋转矩形对象
cv::RotatedRect rotatedRect(center, size, angle);

// 访问旋转矩形的属性
cv::Point2f rectCenter = rotatedRect.center;
cv::Size2f rectSize = rotatedRect.size;
float rectAngle = rotatedRect.angle;

// 输出旋转矩形的属性
std::cout << "Center: (" << rectCenter.x << ", " << rectCenter.y << ")" << std::endl;
std::cout << "Size: " << rectSize.width << " x " << rectSize.height << std::endl;
std::cout << "Angle: " << rectAngle << " degrees" << std::endl;

return 0;
}

cv::RotatedRect 类在许多图像处理和计算机视觉任务中非常有用,特别是在需要表示旋转物体的边界框时。它可以帮助您存储和操作旋转矩形的属性,以及在图像中进行相关操作,例如绘制旋转矩形、计算旋转矩形的边界框等。


cv::Point

在 OpenCV 库中,cv::Point 是一个用于表示二维坐标点的类。它通常用于图像处理和计算机视觉中,用于表示像素位置、图像上的点、图像中的特征点等。

cv::Point 的定义位于 opencv2/core.hpp 头文件中,其结构如下:

1
2
3
4
5
6
7
8
class Point {
public:
Point();
Point(int x, int y);
Point(const Point& pt);

int x, y;
};

这个类有以下几个构造函数和成员变量:

  • Point(): 默认构造函数,会将坐标点初始化为 (0, 0)。
  • Point(int x, int y): 构造函数,传入 x 和 y 坐标值。
  • Point(const Point& pt): 复制构造函数,用于创建一个已有点的副本。
  • xy: 整数类型的成员变量,表示点的 x 和 y 坐标。

下面是一个示例代码,演示了如何使用 cv::Point 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <opencv2/opencv.hpp>

int main() {
// 创建一个点对象
cv::Point pt1(10, 20);

// 访问点的坐标
int x = pt1.x;
int y = pt1.y;

// 输出点的坐标
std::cout << "Point coordinates: (" << x << ", " << y << ")" << std::endl;

// 创建另一个点对象
cv::Point pt2(5, 8);

// 使用复制构造函数创建新点对象
cv::Point pt3 = pt2;

return 0;
}

cv::Point 类在许多情况下都非常有用,例如在图像上标记特定位置、表示特征点、进行坐标计算等。它是 OpenCV 中基本的数据结构之一,用于处理图像中的点和坐标。


cv::Scalar

cv::Scalar 是 OpenCV 库中的一个类,用于表示一个包含四个分量的向量,通常用于表示颜色、像素值等。这四个分量可以分别表示蓝色、绿色、红色和透明度(BGR-A)。

这个类在图像处理中经常用于设置颜色、像素值等。每个分量都是一个双精度浮点数,通常取值范围是 0 到 255,表示一个 8 位无符号整数值的颜色分量。

下面是一个简单的例子,演示了如何使用 cv::Scalar 来创建颜色以及像素值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <opencv2/opencv.hpp>

int main() {
// 创建一个红色的 Scalar 对象
cv::Scalar redColor(0, 0, 255);

// 创建一个白色的 Scalar 对象
cv::Scalar whiteColor(255, 255, 255);

// 输出 Scalar 对象的分量值
std::cout << "Red Color: " << redColor[0] << ", " << redColor[1] << ", " << redColor[2] << std::endl;

// 创建一个像素值为 (100, 150, 200) 的 Scalar 对象
cv::Scalar pixelValue(100, 150, 200);

return 0;
}

需要注意的是,这里提供的颜色顺序是 BGR,与一般人们常见的 RGB 顺序不同。另外,如果不需要使用透明度,可以忽略透明度分量。

cv::Scalar 通常用于填充矩形、绘制文本、设置像素值等场景,方便地表示颜色和值。在图像处理和计算机视觉领域中,cv::Scalar 是一个很有用的工具,使颜色和值的处理变得简单且易于理解。


cv::Mat

  • 简介:

    • cv::Mat 是 OpenCV 库中表示图像和矩阵的核心数据结构之一。它是一个多维数组,可以存储和操作图像、矩阵和其他类型的数据
  • 原型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    class Mat {
    public:
    // 构造函数
    Mat();
    Mat(int rows, int cols, int type);
    Mat(Size size, int type);
    Mat(int rows, int cols, int type, const Scalar& scalar);
    Mat(Size size, int type, const Scalar& scalar);
    Mat(const Mat& m);

    // 数据访问
    int rows;
    int cols;
    int type();
    int channels();
    int depth();
    size_t elemSize();
    size_t elemSize1();
    bool empty();
    void release();
    Mat clone() const;
    Mat& operator=(const Mat& m);
    Mat operator()(const Range& rowRange, const Range& colRange) const;
    Mat row(int y) const;
    Mat col(int x) const;
    Mat rowRange(int startrow, int endrow) const;
    Mat colRange(int startcol, int endcol) const;
    Mat reshape(int cn, int rows = 0) const;

    // 数据操作
    void create(int rows, int cols, int type);
    void create(Size size, int type);
    void convertTo(Mat& m, int rtype, double alpha = 1, double beta = 0) const;
    void copyTo(Mat& m) const;
    void copyTo(Mat& m, const Mat& mask) const;
    void assignTo(Mat& m, int type = -1) const;
    void swap(Mat& m);
    Mat& setTo(const Scalar& scalar, const Mat& mask = Mat());
    Mat& setTo(const Scalar& scalar, const Mat& mask);
    Mat reshape(int cn, int newndims, const int* newsz) const;

    // 图像加载和保存
    static Mat imread(const String& filename, int flags = IMREAD_COLOR);
    bool imwrite(const String& filename, const std::vector<int>& params = std::vector<int>()) const;

    // 其他函数和操作符重载
    // ...
    };
  • 详解:

    • cv::Mat 的主要特点和功能包括
      • 多维数组表示:cv::Mat 可以表示多维数组,如二维图像(矩阵)和三维图像(彩色图像)等。
      • 数据访问:可以通过 rows 和 cols 成员变量获取矩阵的行数和列数,通过 at 成员函数或数组索引操作符 [] 来访问和修改矩阵中的元素。
      • 数据操作:可以对 cv::Mat 执行各种数据操作,如创建矩阵、复制矩阵、转换数据类型、调整大小、提取子矩阵等
      • 图像加载和保存:通过 imread 和 imwrite 函数可以加载和保存图像文件
      • 运算符重载:cv::Mat 支持许多运算符重载,如加法、减法、乘法等,以及与其他 cv::Mat 对象之间的操作
  • 注:

    • 需要注意的是,cv::Mat 的默认构造函数创建了一个空的矩阵对象,必须使用 create 函数来分配内存空间并指定矩阵的大小和数据类型
    • 使用 cv::Mat,你可以进行各种图像处理和计算机视觉任务,如图像加载、图像处理、特征提取、模式识别等。了解和熟悉 cv::Mat 的功能和使用方法是使用 OpenCV 进行图像处理和计算机视觉开发的基础。你可以参考 OpenCV 官方文档和示例代码,深入学习关于 cv::Mat 的更多信息和用法

cv::imread

  • 简介:

    • cv::imread() 是 OpenCV 库中用于读取图像文件的函数。它接受图像文件的路径作为输入,并返回一个表示图像的 cv::Mat 对象
  • 原型:

    1
    cv::Mat cv::imread(const cv::String& filename, int flags = cv::IMREAD_COLOR);
  • 参数:

    • filename:图像文件的路径
    • flags(可选):指定读取图像的方式,默认值为 cv::IMREAD_COLOR。可以使用以下标志来指定读取图像的颜色模式
      • cv::IMREAD_COLOR:读取彩色图像(默认值)
      • cv::IMREAD_GRAYSCALE:将图像以灰度模式读取
      • cv::IMREAD_UNCHANGED:以原始图像的包含 alpha 通道的方式读取
  • 返回值:

    • 如果成功读取图像文件,则返回一个 cv::Mat 对象,表示读取到的图像数据
    • 如果无法读取图像文件,返回一个空的 cv::Mat 对象
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <opencv2/opencv.hpp>

    int main() {
    // 读取图像文件
    cv::Mat image = cv::imread("image.jpg");

    if (image.empty()) {
    std::cout << "无法加载图像文件" << std::endl;
    return -1;
    }

    // 处理图像...

    return 0;
    }
  • 注:

    • 在上述示例中,cv::imread() 函数尝试加载名为 “image.jpg” 的图像文件。如果成功读取图像文件,将返回一个非空的 cv::Mat 对象,可以对其进行进一步的图像处理操作
    • 需要注意的是,cv::imread() 函数需要提供正确的图像文件路径,并确保所使用的图像格式与 OpenCV 支持的图像格式相匹配。如果无法加载图像文件,可以检查文件路径是否正确、文件是否存在以及文件格式是否受支持
    • 另外,OpenCV 还提供了其他的图像读取函数,如 cv::imdecode() 用于从内存中读取图像数据,以及 cv::VideoCapture 类用于读取视频文件或摄像头视频流。你可以根据具体的需求选择适当的图像读取方法

cv::imwrite

  • 简介:

    • cv::imwrite() 是 OpenCV 库中用于将图像数据保存到文件的函数。它接受图像数据和目标文件路径作为输入,并将图像数据写入指定的文件
  • 原型:

    1
    bool cv::imwrite(const cv::String& filename, const cv::Mat& image, const std::vector<int>& params = std::vector<int>());
  • 参数:

    • filename:保存图像的文件路径。
    • image:要保存的图像数据,为一个 cv::Mat 对象。
    • params(可选):保存图像的参数,为一个整数向量。参数可以控制图像的压缩格式、质量等。常用的参数包括:
      • cv::IMWRITE_JPEG_QUALITY:指定 JPEG 格式的图像质量,取值范围为 0 到 100。
      • cv::IMWRITE_PNG_COMPRESSION:指定 PNG 格式的压缩级别,取值范围为 0 到 9,其中 0 表示无压缩,9 表示最高压缩
  • 返回值:

    • 如果成功将图像数据写入文件,则返回 true
    • 如果无法将图像数据写入文件,则返回 false
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <opencv2/opencv.hpp>

    int main() {
    // 读取图像文件
    cv::Mat image = cv::imread("image.jpg");

    if (image.empty()) {
    std::cout << "无法加载图像文件" << std::endl;
    return -1;
    }

    // 保存图像文件
    bool result = cv::imwrite("output.jpg", image);

    if (result) {
    std::cout << "图像保存成功" << std::endl;
    } else {
    std::cout << "图像保存失败" << std::endl;
    }

    return 0;
    }
  • 注:

    • 在上述示例中,cv::imread() 函数尝试加载名为 “image.jpg” 的图像文件,然后将图像数据存储在 cv::Mat 对象 image 中。接下来,通过调用 cv::imwrite() 函数,将图像数据保存到名为 “output.jpg” 的文件中
    • 需要注意的是,cv::imwrite() 函数需要提供正确的文件路径,并确保所指定的文件格式与 OpenCV 支持的图像格式相匹配。如果无法将图像数据写入文件,可以检查文件路径是否正确、文件是否可写以及图像数据是否有效
    • 另外,OpenCV 还提供了其他的图像保存函数,如 cv::imencode() 用于将图像数据编码为内存缓冲区,并将其保存为图像文件。你可以根据具体的需求选择适当的图像保存方法

cv::rectangle

  • 简介:

    • cv::rectangle() 是 OpenCV 库中用于在图像上绘制矩形的函数。它接受一个图像对象和矩形参数,并在图像上绘制指定的矩形
  • 原型:

    1
    void cv::rectangle(cv::Mat& img, const cv::Rect& rect, const cv::Scalar& color, int thickness = 1, int lineType = cv::LINE_8, int shift = 0);
  • 参数:

    • img:要在其上绘制矩形的图像对象,为一个 cv::Mat 对象。
    • rect:矩形的位置和大小,为一个 cv::Rect 对象。
    • color:矩形的颜色,为一个 cv::Scalar 对象,可以是 BGR 或灰度值。
    • thickness(可选):矩形线条的粗细,默认值为 1。
    • lineType(可选):线条的类型,默认值为 cv::LINE_8,表示8连通线条。
    • shift(可选):点坐标的小数位数,默认值为 0
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include <opencv2/opencv.hpp>

    int main() {
    // 创建一个空白图像
    cv::Mat image(300, 400, CV_8UC3, cv::Scalar(255, 255, 255));

    // 绘制矩形
    cv::Rect rect(50, 50, 200, 150);
    cv::Scalar color(0, 0, 255); // 红色
    int thickness = 2;
    int lineType = cv::LINE_8;
    int shift = 0;
    cv::rectangle(image, rect, color, thickness, lineType, shift);

    // 显示图像
    cv::imshow("Image", image);
    cv::waitKey(0);

    return 0;
    }
  • 注:

    • 在上述示例中,我们创建了一个大小为 300x400 像素、颜色为白色的图像 image。然后,我们定义了一个矩形 rect,它位于图像上的 (50, 50) 点,宽度为 200,高度为 150。我们将矩形的颜色设置为红色,线条粗细为 2。最后,我们使用 cv::rectangle() 函数在图像上绘制矩形
    • 需要注意的是,cv::rectangle() 函数会在原始图像对象上进行绘制,因此在函数调用之前,确保图像对象已经被创建并且是可写的
    • 除了 cv::rectangle(),OpenCV 还提供了其他的绘制函数,如绘制直线、圆形、多边形等。你可以根据具体的需求选择适当的绘制函数,并使用不同的参数来调整绘制效果

cv::imshow

  • 简介:

    • cv::imshow() 是 OpenCV 库中用于显示图像的函数。它接受一个窗口名称和图像对象,并在一个新的窗口中显示图像
  • 原型:

    1
    void cv::imshow(const cv::String& winname, const cv::Mat& image);
  • 参数:

    • winname:窗口的名称,为一个字符串。
    • image:要显示的图像数据,为一个 cv::Mat 对象
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <opencv2/opencv.hpp>

    int main() {
    // 读取图像文件
    cv::Mat image = cv::imread("image.jpg");

    if (image.empty()) {
    std::cout << "无法加载图像文件" << std::endl;
    return -1;
    }

    // 显示图像
    cv::imshow("Image", image);

    // 等待按下任意按键
    cv::waitKey(0);

    }
  • 注:

    • 在上述示例中,我们使用 cv::imread() 函数加载一个名为 “image.jpg” 的图像文件,并将图像数据存储在 cv::Mat 对象 image 中。然后,我们调用 cv::imshow() 函数,将图像显示在一个名为 “Image” 的窗口中
    • 需要注意的是,cv::imshow() 函数会创建一个新的窗口来显示图像。窗口名称是唯一的,可以使用不同的名称来创建多个窗口并显示不同的图像。如果窗口名称已经存在,则会将新的图像显示在现有窗口中
    • 在调用 cv::imshow() 函数后,通常需要调用 cv::waitKey() 函数来等待用户按下按键。这样可以使窗口保持打开状态,直到用户关闭窗口或按下按键。cv::waitKey() 函数返回按下的键的 ASCII 值,如果没有按下按键,则返回 -1
    • 除了 cv::imshow(),OpenCV 还提供了其他的图像显示和交互函数,如调整窗口大小、绘制图形、注册鼠标事件等。你可以根据具体的需求选择适当的函数,并使用相关的参数和事件处理机制来进行图像显示和交互操作

namedWindow

  • 简介:

    • cv::namedWindow() 是 OpenCV 库中用于创建一个带有指定名称的窗口的函数。它可以用于显示图像、绘制图形和接收用户交互事件
  • 原型:

    1
    void cv::namedWindow(const cv::String& winname, int flags = cv::WINDOW_AUTOSIZE);
  • 参数:

    • winname:窗口的名称,为一个字符串。
    • flags(可选):窗口的属性标志,用于指定窗口的行为和外观。常用的标志包括:
      • cv::WINDOW_NORMAL:创建一个可调整大小的窗口。
      • cv::WINDOW_AUTOSIZE:创建一个自适应大小的窗口,窗口大小将根据图像大小自动调整
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <opencv2/opencv.hpp>

    int main() {
    // 读取图像文件
    cv::Mat image = cv::imread("image.jpg");

    if (image.empty()) {
    std::cout << "无法加载图像文件" << std::endl;
    return -1;
    }

    // 创建一个名为 "Image" 的窗口
    cv::namedWindow("Image", cv::WINDOW_NORMAL);

    // 显示图像
    cv::imshow("Image", image);

    // 等待按下任意按键
    cv::waitKey(0);

    return 0;
    }
  • 注:

    • 在上述示例中,我们使用 cv::imread() 函数加载一个名为 “image.jpg” 的图像文件,并将图像数据存储在 cv::Mat 对象 image 中。然后,我们调用 cv::namedWindow() 函数,创建一个名为 “Image” 的窗口。通过设置 flags 参数为 cv::WINDOW_NORMAL,我们可以创建一个可调整大小的窗口
    • 接下来,我们调用 cv::imshow() 函数将图像显示在 “Image” 窗口中。最后,通过调用 cv::waitKey() 函数等待按键,以保持窗口打开状态,直到用户关闭窗口或按下按键
    • 使用 cv::namedWindow() 函数可以为窗口指定名称,并设置窗口的属性标志以控制其行为和外观。这样可以在显示多个图像时,为每个图像创建独立的窗口,并根据需要进行大小调整

cv::destroyWindow

  • 简介:

    • cv::destroyWindow() 是 OpenCV 库中用于关闭指定名称的窗口的函数。它可以用于关闭已创建的窗口并释放与窗口相关的资源
  • 原型:

    1
    void cv::destroyWindow(const cv::String& winname);
  • 参数:

    • winname:要关闭的窗口的名称,为一个字符串
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <opencv2/opencv.hpp>

    int main() {
    // 创建一个名为 "Image" 的窗口
    cv::namedWindow("Image");

    // ... 一些图像处理操作 ...

    // 关闭名为 "Image" 的窗口
    cv::destroyWindow("Image");

    return 0;
    }
  • 注:

    • 在上述示例中,我们通过调用 cv::namedWindow() 函数创建了一个名为 “Image” 的窗口。然后,在执行一些图像处理操作后,我们通过调用 cv::destroyWindow() 函数关闭了名为 “Image” 的窗口
    • 使用 cv::destroyWindow() 函数可以在不需要显示某个窗口时,将其关闭并释放相关资源。这对于图像处理过程中动态创建和关闭窗口非常有用
    • 除了关闭单个窗口,还可以使用 cv::destroyAllWindows() 函数关闭所有已创建的窗口,释放所有窗口相关的资源
    • 请注意,窗口的创建和销毁是在 OpenCV 的 HighGUI 模块中进行的,因此在使用这些函数之前,需要确保已经正确链接和初始化了 OpenCV 库

cv::waitKey

  • 简介:

    • cv::waitKey() 是 OpenCV 库中用于等待用户按下按键的函数。它在程序执行过程中暂停,并等待用户按下键盘上的按键
  • 原型:

    1
    int cv::waitKey(int delay = 0);
  • 参数:

    • delay(可选):等待按键的时间(以毫秒为单位),默认值为 0。如果设置为正整数,表示等待指定的时间后,如果没有按键按下,则函数将继续执行。如果设置为 0,则函数将无限期等待按键的按下
  • 返回值:

    • 返回按下的键的 ASCII 值。
    • 如果在指定的时间内没有按键按下,或者未能获取按键的值,则返回 -1。
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <opencv2/opencv.hpp>

    int main() {
    // 创建一个名为 "Image" 的窗口
    cv::namedWindow("Image");

    // 显示图像
    cv::Mat image = cv::imread("image.jpg");
    cv::imshow("Image", image);

    // 等待按下任意按键
    int key = cv::waitKey(0);

    // 输出按下的键的 ASCII 值
    std::cout << "按下的键的 ASCII 值: " << key << std::endl;

    return 0;
    }
  • 注:

    • 在上述示例中,我们通过调用 cv::namedWindow() 函数创建了一个名为 “Image” 的窗口,并通过 cv::imshow() 函数显示了一张图像。然后,我们调用 cv::waitKey() 函数,等待用户按下键盘上的按键。将 delay 参数设置为 0 表示无限期等待按键按下
    • 当用户按下按键时,cv::waitKey() 函数将返回按键的 ASCII 值。我们可以通过输出语句将其打印出来。如果没有按键按下或无法获取按键的值,函数将返回 -1
    • 需要注意的是,调用 cv::waitKey() 函数会使程序暂停执行,直到用户按下按键或达到指定的等待时间。这通常与图像显示和交互操作配合使用,以保持窗口打开状态并等待用户操作
    • 除了返回按键的 ASCII 值,cv::waitKey() 函数还可以用于实现视频播放和实时图像处理等应用,其中按键事件用于控制程序的流程和操作

简介

  • OpenCV 相关编程技巧

opencv 画点

在 OpenCV 中,你可以使用 cv2.circle 方法在图像上画点。尽管 cv2.circle 的主要用途是画圆,但通过设置非常小的半径(例如 1 或 2 像素),可以用它来画点。

以下是一个简单的例子:

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2
import numpy as np

# 创建一个空白图像(黑色背景)
image = np.zeros((500, 500, 3), dtype=np.uint8)

# 定义点的坐标
point = (250, 250)

# 用白色(255, 255, 255)画一个点,点的半径设置为1
cv2.circle(image, point, radius=1, color=(255, 255, 255), thickness=-1)

# 显示图像
cv2.imshow("Point", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

说明

  1. point: 点的坐标,格式为 (x, y)
  2. radius=1: 点的大小,这里设置为 1 个像素。
  3. color=(255, 255, 255): 点的颜色,这里是白色。
  4. thickness=-1: 线条厚度,设置为 -1 表示填充。

如果需要画多个点

你可以使用一个循环来绘制多个点:

1
2
3
4
5
# 多个点的坐标
points = [(100, 100), (200, 200), (300, 300)]

for point in points:
cv2.circle(image, point, radius=1, color=(0, 255, 0), thickness=-1) # 用绿色画点

注意事项

  • 如果你在灰度图像上画点,需要确保颜色值是一个整数,例如 255
  • 如果需要更精确的绘图,例如支持亚像素精度,可以使用 OpenCV 的其他绘图工具或库如 Matplotlib。

如果有更多需求,例如绘制带标签的点或动画点,请告诉我!

多张图像编码为视频

  • MJPEG –> avi
  • mp4v –> mp4

图像的滤波平滑处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//均值滤波
void blur(InputArray src, OutputArray dst,
Size ksize, Point anchor = Point(-1,-1),
int borderType = BORDER_DEFAULT);
//src参数表示输入图像,dst参数表示输出图像,与输入图像有同样的size和type;ksize参数表示核的大小
//anchor参数表示核的中心,默认值为Point(-1,-1),会转化为核的中点;borderType参数表示边缘点插值类型

//中值滤波
void medianBlur(InputArray src, OutputArray dst, int ksize);
//src参数表示输入图像,dst参数表示输出图像,与输入图像有同样的size和type;ksize参数表示正方形的边长. 边长必须是奇数而且大于1
//需要注意的是,当ksize是3或5的时候,图像的深度可以是CV_8U、CV_16U或CV_32F,对于更大的ksize,那么图像的深度只能是CV_8U

//高斯滤波
void GaussianBlur(InputArray src, OutputArray dst, Size ksize,
double sigmaX, double sigmaY = 0,
int borderType = BORDER_DEFAULT);
//src参数表示输入图像,dst参数表示输出图像,与输入图像有同样的size和type;ksize参数表示高斯核大小
//ksize.width和ksize.height可以不同,但是都必须是正整数而且是奇数,也可以是O,如果是0的话,它的值由sigmaX和sigmaY决定
//sigmaX参数表示X方向上的标准差,sigmaY参数表示Y方向上的标准差,如果Y方向为0,则被设置和X方向一样,如果X和Y方向都为0,则它们由ksize.width和ksize.height计算得到,建议全部都给定数值。
//borderType参数表示边缘点插值类型

//双边滤波器
void bilateralFilter( InputArray src, OutputArray dst, int d,
double sigmaColor, double sigmaSpace,
int borderType = BORDER_DEFAULT );
//src参数表示输入图像为单通道或3通道的8比特图像,dst参数表示输出图像,与输入图像有同样的size和type
//d参数表示每个像素领域的直径,如果为负数,则由sigmaSpace确定
//sigmaColor参数表示颜色空间的标准偏差,sigmaSpace表示坐标空间的标准偏差,borderType参数表示边缘点插值类型

cv::Point 乘以0.5

要实现 cv::Point 乘以标量的操作,你可以直接使用 cv::Point 类的成员函数 operator*= 或者全局函数 operator*。这两个函数支持将 cv::Point 与标量相乘。

以下是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <opencv2/core.hpp>

int main() {
cv::Point pt1(2, 4);

// 使用 operator*=
pt1 *= 0.5;

// 使用 operator*
cv::Point pt2 = pt1 * 0.5;

std::cout << "pt1: (" << pt1.x << ", " << pt1.y << ")" << std::endl;
std::cout << "pt2: (" << pt2.x << ", " << pt2.y << ")" << std::endl;

return 0;
}

在这个示例中,pt1 被乘以0.5,结果存储在 pt1 中,然后再将 pt1 乘以0.5,结果存储在 pt2 中。最后输出两个点的坐标。

你也可以通过直接使用 operator* 来将 cv::Point 与标量相乘,结果会存储在新的 cv::Point 中。

C++ OpenCV 图片 画上指定字符串

要在C++中使用OpenCV在图像上添加指定的字符串,你可以按照以下步骤进行操作:

  1. 导入必要的头文件:
1
2
3
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
  1. 读取图像文件:
1
cv::Mat image = cv::imread("input_image.jpg");
  1. 定义要添加的文本内容以及其他绘制参数:
1
2
3
4
5
6
std::string text = "Your text here";
cv::Point org(50, 50); // 文本的起始位置
int fontFace = cv::FONT_HERSHEY_SIMPLEX; // 字体类型
double fontScale = 1.0; // 字体缩放系数
cv::Scalar color(255, 255, 255); // 文本颜色
int thickness = 2; // 文本粗细
  1. 在图像上绘制文本:
1
cv::putText(image, text, org, fontFace, fontScale, color, thickness);
  1. 显示结果图像:
1
2
cv::imshow("Image with Text", image);
cv::waitKey(0);

完整示例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

int main() {
// 读取图像文件
cv::Mat image = cv::imread("input_image.jpg");

// 定义要添加的文本内容以及其他绘制参数
std::string text = "Your text here";
cv::Point org(50, 50); // 文本的起始位置
int fontFace = cv::FONT_HERSHEY_SIMPLEX; // 字体类型
double fontScale = 1.0; // 字体缩放系数
cv::Scalar color(255, 255, 255); // 文本颜色
int thickness = 2; // 文本粗细

// 在图像上绘制文本
cv::putText(image, text, org, fontFace, fontScale, color, thickness);

// 显示结果图像
cv::imshow("Image with Text", image);
cv::waitKey(0);

return 0;
}

确保将 "input_image.jpg" 替换为你实际要操作的图像文件路径。

OpenCV 将图片编码成jpg格式并保存在内存中

要将图像编码为JPEG格式并保存在内存中,您可以使用 cv::imencode 函数。以下是一个示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <opencv2/opencv.hpp>
#include <vector>

int main() {
// 读取图像
cv::Mat image = cv::imread("input_image.png"); // 替换为您的图像文件路径

// 检查图像是否成功加载
if (image.empty()) {
std::cerr << "Failed to load image." << std::endl;
return 1;
}

// 编码图像为JPEG格式
std::vector<uchar> buffer;
std::vector<int> params = {cv::IMWRITE_JPEG_QUALITY, 95}; // JPEG质量设置为95
cv::imencode(".jpg", image, buffer, params);

// 检查编码是否成功
if (buffer.empty()) {
std::cerr << "Failed to encode image." << std::endl;
return 1;
}

// 在此处可以将 buffer 保存到内存中或者传输到其他地方

std::cout << "Image encoded successfully." << std::endl;

return 0;
}

在这个示例中,我们首先使用 cv::imread 函数加载了一个图像(这里假设图像是PNG格式的)。然后,我们使用 cv::imencode 函数将图像编码为JPEG格式,并将结果保存到一个名为 bufferstd::vector 中。我们还可以通过 params 参数设置JPEG的质量。最后,我们检查了编码是否成功,并打印出相应的消息。

您可以在代码的注释中看到如何将 buffer 保存到内存中或者传输到其他地方。

OpenCV 给出左上角和右下角的两个点的坐标 在图像上画出对应的矩形框

您可以使用OpenCV中的 cv::rectangle 函数来在图像上画出矩形框。下面是一个示例代码,它接受左上角和右下角的点坐标,并在图像上绘制出对应的矩形框:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <opencv2/opencv.hpp>

int main() {
// 读取图像
cv::Mat image = cv::imread("input_image.jpg");

// 确定矩形框的左上角和右下角点坐标
cv::Point topLeft(100, 100); // 左上角坐标
cv::Point bottomRight(300, 400); // 右下角坐标

// 在图像上画出矩形框
cv::rectangle(image, topLeft, bottomRight, cv::Scalar(0, 255, 0), 2);

// 显示绘制矩形框后的图像
cv::imshow("Rectangle", image);
cv::waitKey(0);

return 0;
}

在这个示例中,我们首先读取了一张图像。然后,我们定义了矩形框的左上角和右下角点的坐标。接下来,我们使用 cv::rectangle 函数在图像上绘制矩形框,该函数的参数包括图像,左上角和右下角的点坐标,以及矩形框的颜色和线条宽度。最后,我们显示了绘制了矩形框的图像。

确保将 "input_image.jpg" 替换为您实际的图像文件路径。

OpenCV cv::Mat 获取图像在内存中字节大小

在OpenCV中,可以使用cv::Mattotal()函数和elemSize()函数来获取图像在内存中的字节大小。具体地说,total()函数返回矩阵中元素的总数,而elemSize()函数返回每个元素的字节大小。通过将这两个值相乘,你可以得到整个矩阵的字节大小。

以下是一个示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <opencv2/opencv.hpp>

int main() {
// 读取图像
cv::Mat image = cv::imread("path/to/your/image.jpg");

if (image.empty()) {
std::cerr << "Error: Couldn't read the image." << std::endl;
return -1;
}

// 计算图像在内存中的字节大小
size_t byteSize = image.total() * image.elemSize();

std::cout << "Image size in bytes: " << byteSize << std::endl;

return 0;
}

请确保你已经安装了OpenCV库,并在编译时链接了相应的库文件。上述示例代码中,path/to/your/image.jpg应替换为实际图像的文件路径。

OpenCV 在图像上绘制旋转矩形框 函数

要在C++中使用OpenCV在图像上绘制旋转矩形框,您可以使用以下步骤:

  1. 首先,确保您已经包含OpenCV库的头文件和命名空间:
1
2
#include <opencv2/opencv.hpp>
using namespace cv;
  1. 创建一个旋转矩形框的数据结构,通常使用RotatedRect类来表示。该类包含了旋转矩形的中心坐标、大小和角度。
1
RotatedRect rotatedRect(Point2f(centerX, centerY), Size2f(width, height), angle);

在上述代码中,centerXcenterY是旋转矩形框的中心坐标,widthheight是矩形框的宽度和高度,angle是旋转角度(以度为单位)。

  1. 创建一个空白图像或使用已有的图像作为绘制目标。
1
Mat image = imread("input.jpg");
  1. 使用ellipse()函数绘制旋转矩形框。您可以将旋转矩形的参数传递给该函数,然后选择颜色和线宽来绘制矩形框。
1
2
3
4
Scalar color(0, 255, 0); // 颜色(BGR格式)
int thickness = 2; // 线宽

ellipse(image, rotatedRect, color, thickness);
  1. 最后,您可以选择将绘制后的图像保存到磁盘上或显示它。

完整的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
Mat image = imread("input.jpg");
if (image.empty()) {
// 处理图像加载失败的情况
return -1;
}

Point2f center(image.cols / 2, image.rows / 2);
float width = 200; // 矩形框的宽度
float height = 100; // 矩形框的高度
float angle = 45; // 旋转角度(以度为单位)

RotatedRect rotatedRect(center, Size2f(width, height), angle);

Scalar color(0, 255, 0); // 颜色(BGR格式)
int thickness = 2; // 线宽

ellipse(image, rotatedRect, color, thickness);

imshow("Rotated Rectangle", image);
waitKey(0);

return 0;
}

这将加载名为”input.jpg”的图像,在图像上绘制一个旋转的矩形框,并显示在窗口中。您可以根据需要调整图像文件名、矩形框参数以及颜色和线宽。

OpenCV cv::Rect 获取四个点的cv::Point

要从一个 cv::Rect 对象中获取其四个角的 cv::Point,你可以使用 cv::Rect 的成员函数 tl()br(),分别表示矩形的左上角和右下角。然后,可以使用这两个点来构建其他两个角的坐标。以下是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <opencv2/opencv.hpp>

int main() {
// 创建一个矩形
cv::Rect rect(100, 100, 200, 150);

// 获取矩形的四个角
cv::Point tl = rect.tl(); // 左上角
cv::Point br = rect.br(); // 右下角

// 计算其他两个角的坐标
cv::Point tr(rect.x + rect.width, rect.y); // 右上角
cv::Point bl(rect.x, rect.y + rect.height); // 左下角

// 打印四个角的坐标
std::cout << "左上角: " << tl << std::endl;
std::cout << "右上角: " << tr << std::endl;
std::cout << "左下角: " << bl << std::endl;
std::cout << "右下角: " << br << std::endl;

return 0;
}

在上述示例中,我们首先创建了一个 cv::Rect 对象,然后使用 tl()br() 方法分别获取了左上角和右下角的坐标。接着,我们使用矩形的属性 xywidthheight 来计算右上角和左下角的坐标。最后,我们打印出四个角的坐标。这样,你就可以获取矩形的所有四个角的 cv::Point 坐标。

cv::Mat(cv::Rect) 是什么用法

cv::Mat 的构造函数可以接受一个 cv::Rect 参数,用于创建一个新的 cv::Mat 对象,该对象表示了原始图像中指定区域的像素数据。这可以用于从原始图像中提取感兴趣的区域(Region of Interest,ROI)。

构造函数的用法如下:

1
cv::Mat::Mat(const cv::Mat& m, const Rect& roi);

其中,m 是原始图像,roi 是一个 cv::Rect 对象,表示要提取的区域。cv::Mat 对象将只包含 roi 指定的区域的像素数据,而不包含整个原始图像的数据。

以下是一个示例,演示如何使用 cv::Mat 的构造函数来提取图像中的感兴趣区域:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <opencv2/opencv.hpp>

int main() {
// 读取图像
cv::Mat image = cv::imread("image.jpg");

// 定义感兴趣区域的矩形
cv::Rect roiRect(100, 100, 200, 150); // (x, y, width, height)

// 使用构造函数提取感兴趣区域
cv::Mat roi = cv::Mat(image, roiRect);

// 显示原始图像和感兴趣区域
cv::imshow("原始图像", image);
cv::imshow("感兴趣区域", roi);
cv::waitKey(0);

return 0;
}

在上述示例中,我们首先定义了一个矩形 roiRect,表示感兴趣区域的位置和大小。然后,使用 cv::Mat 的构造函数,我们从原始图像中提取了这个感兴趣区域,最后将原始图像和感兴趣区域显示出来。这种方法允许你对感兴趣区域进行进一步的处理,而无需复制整个图像数据。

OpenCV 检查矩形框是否在四个点围成的四边型之内

要检查一个矩形框是否在由四个点围成的四边形内部,你可以使用 OpenCV 提供的点与多边形关系判断函数。以下是一个示例,演示了如何使用 OpenCV 检查一个矩形框是否在四个点围成的四边形内部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <opencv2/opencv.hpp>

using namespace cv;

bool isRectangleInsideQuadrilateral(const Point2f& p1, const Point2f& p2, const Point2f& p3, const Point2f& p4, const Rect& rect) {
std::vector<Point2f> quadPoints = {p1, p2, p3, p4};
cv::Mat quadMat = cv::Mat(quadPoints).reshape(1);
cv::convexHull(quadMat, quadMat);

std::vector<Point2f> rectPoints(4);
rectPoints[0] = Point2f(rect.x, rect.y);
rectPoints[1] = Point2f(rect.x + rect.width, rect.y);
rectPoints[2] = Point2f(rect.x + rect.width, rect.y + rect.height);
rectPoints[3] = Point2f(rect.x, rect.y + rect.height);

for (const Point2f& point : rectPoints) {
if (pointPolygonTest(quadMat, point, false) < 0) {
return false;
}
}

return true;
}

int main() {
Point2f p1(100, 100);
Point2f p2(300, 100);
Point2f p3(300, 200);
Point2f p4(100, 200);

Rect rect(150, 150, 50, 50); // 矩形框在四边形内部

if (isRectangleInsideQuadrilateral(p1, p2, p3, p4, rect)) {
std::cout << "Rectangle is inside the quadrilateral." << std::endl;
} else {
std::cout << "Rectangle is not inside the quadrilateral." << std::endl;
}

return 0;
}

在上述示例中,我们首先定义了四个点的坐标(p1p2p3p4)以及一个矩形框(rect)。然后,我们使用 isRectangleInsideQuadrilateral 函数来检查矩形框是否在由这四个点围成的四边形内部。

isRectangleInsideQuadrilateral 函数中,我们首先将四个点构成的四边形进行凸包处理,然后使用 pointPolygonTest 函数来检查矩形框的每个顶点是否在凸包内。如果所有顶点都在凸包内,那么矩形框就在四边形内部。

运行示例代码后,根据矩形框的位置关系,程序将输出相应的信息。

OpenCV 四个点cv::Point创建一个矩形框cv::Rect

在OpenCV中,你可以使用四个点的坐标来创建一个包围这些点的矩形框。可以使用这些坐标来计算出最小包围矩形,并将其转换为cv::Rect对象。以下是一个示例,演示了如何使用四个点的坐标来创建一个矩形框:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <opencv2/opencv.hpp>

using namespace cv;

int main() {
// 定义四个点的坐标
Point pt1(100, 100);
Point pt2(300, 100);
Point pt3(300, 200);
Point pt4(100, 200);

// 计算包围四个点的最小矩形框
RotatedRect rotatedRect = minAreaRect(std::vector<Point2f>{pt1, pt2, pt3, pt4});

// 将RotatedRect对象转换为Rect对象
Rect boundingRect = rotatedRect.boundingRect();

std::cout << "Bounding Rect: (x=" << boundingRect.x << ", y=" << boundingRect.y
<< ", width=" << boundingRect.width << ", height=" << boundingRect.height << ")" << std::endl;

return 0;
}

在上述示例中,我们首先定义了四个点的坐标(pt1pt2pt3pt4)。然后,我们使用minAreaRect函数计算包围这四个点的最小矩形框,这个矩形框可以是旋转的。最后,我们使用boundingRect成员函数将RotatedRect对象转换为cv::Rect对象,得到矩形框的参数(坐标、宽度和高度)。

运行示例代码后,你将得到最小包围矩形的参数信息。请注意,这个最小包围矩形可能是旋转的,因此其宽度和高度可能与矩形的边界不完全一致。

OpenCV 判断一个一个矩形框是否在另一个矩形框中

在OpenCV中,你可以使用矩形的坐标信息来判断一个矩形是否完全包含另一个矩形。以下是一个示例,展示了如何判断一个矩形是否在另一个矩形内部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <opencv2/opencv.hpp>

using namespace cv;

bool isRectangleInside(const Rect& outerRect, const Rect& innerRect) {
return (innerRect.x >= outerRect.x &&
innerRect.y >= outerRect.y &&
innerRect.x + innerRect.width <= outerRect.x + outerRect.width &&
innerRect.y + innerRect.height <= outerRect.y + outerRect.height);
}

int main() {
Rect outerRect(100, 100, 300, 200); // 外部矩形框
Rect innerRect(150, 150, 100, 50); // 内部矩形框

if (isRectangleInside(outerRect, innerRect)) {
std::cout << "Inner rectangle is inside the outer rectangle." << std::endl;
} else {
std::cout << "Inner rectangle is not inside the outer rectangle." << std::endl;
}

return 0;
}

在上述示例中,我们定义了一个isRectangleInside函数,该函数接受两个Rect对象(矩形框)作为参数,然后通过比较矩形的坐标信息来判断第二个矩形是否完全包含在第一个矩形内部。

请注意,矩形的坐标通常由 (x, y) 表示左上角的点,而 widthheight 分别表示矩形的宽度和高度。在示例中,我们首先创建了一个外部矩形框和一个内部矩形框,并使用isRectangleInside函数判断内部矩形是否在外部矩形内部。

运行示例代码后,根据内部矩形的位置关系,程序将输出相应的信息。

OpenCV 判断一个旋转矩形框是否在一个矩形框中

要判断一个旋转矩形是否完全位于另一个矩形框内部,你可以使用OpenCV的cv::RotatedRectcv::Rect类来实现。以下是一个示例,展示了如何判断一个旋转矩形是否在一个矩形框内部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <opencv2/opencv.hpp>

using namespace cv;

bool isRotatedRectInside(const RotatedRect& outerRect, const Rect& innerRect) {
// 获取旋转矩形框的四个顶点
Point2f vertices[4];
outerRect.points(vertices);

for (int i = 0; i < 4; i++) {
if (vertices[i].x < innerRect.x ||
vertices[i].x > innerRect.x + innerRect.width ||
vertices[i].y < innerRect.y ||
vertices[i].y > innerRect.y + innerRect.height) {
return false; // 旋转矩形框的某个顶点在内部矩形框外部
}
}

return true;
}

int main() {
RotatedRect rotatedRect(Point2f(200, 150), Size2f(200, 100), 45); // 旋转矩形框
Rect innerRect(150, 100, 100, 50); // 内部矩形框

if (isRotatedRectInside(rotatedRect, innerRect)) {
std::cout << "Rotated rectangle is inside the rectangle." << std::endl;
} else {
std::cout << "Rotated rectangle is not inside the rectangle." << std::endl;
}

return 0;
}

在上述示例中,我们定义了一个isRotatedRectInside函数,该函数接受一个旋转矩形框和一个矩形框作为参数,然后通过比较旋转矩形框的顶点坐标和内部矩形框的位置关系来判断旋转矩形是否完全位于内部矩形框内部。

请注意,在判断旋转矩形框是否在矩形框内部时,我们需要获取旋转矩形框的四个顶点坐标,然后检查每个顶点是否在内部矩形框内。这个过程涉及到点与矩形的比较。运行示例代码后,根据旋转矩形的位置关系,程序将输出相应的信息。

OpenCV 获取鼠标位置 图片 详解 示例

要使用OpenCV获取鼠标位置并在图像上显示鼠标位置,你可以遵循以下步骤。这里的示例将使用OpenCV的C++接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <opencv2/opencv.hpp>

using namespace cv;

// 回调函数,用于获取鼠标位置
void onMouse(int event, int x, int y, int flags, void* userdata) {
if (event == EVENT_MOUSEMOVE) {
Mat* image = static_cast<Mat*>(userdata);

// 在图像上绘制鼠标位置
Mat imageCopy = image->clone();
circle(imageCopy, Point(x, y), 5, Scalar(0, 0, 255), -1);
putText(imageCopy, "Mouse Position: (" + std::to_string(x) + ", " + std::to_string(y) + ")",
Point(10, 30), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 255), 2);

imshow("Image with Mouse Position", imageCopy);
}
}

int main() {
// 加载图像
Mat image = imread("image.jpg"); // 替换为你的图像路径

if (image.empty()) {
std::cerr << "Error loading image!" << std::endl;
return -1;
}

namedWindow("Image with Mouse Position");
setMouseCallback("Image with Mouse Position", onMouse, &image);

imshow("Image with Mouse Position", image);

waitKey(0);
destroyAllWindows();

return 0;
}

在这个示例中,我们使用setMouseCallback函数设置鼠标事件的回调函数,当鼠标在图像上移动时会调用这个回调函数。回调函数onMouse获取鼠标的坐标,并在图像上绘制一个红色的圆圈表示鼠标位置,同时在图像上方显示鼠标坐标信息。

你需要将 "image.jpg" 替换为你的图像文件的路径。运行示例代码后,窗口会显示原始图像,并在鼠标移动时在图像上绘制圆圈和坐标信息。

请注意,回调函数是在主循环中被调用的,因此它需要快速执行,以免影响其他的事件处理。

鼠标点击 获取坐标

要在 OpenCV 中获取鼠标点击事件的坐标,你可以使用鼠标事件的回调函数,并在回调函数中获取坐标信息。以下是一个示例代码,演示如何在鼠标点击时输出点的坐标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <opencv2/opencv.hpp>

// 鼠标点击事件的回调函数
void onMouse(int event, int x, int y, int flags, void* userdata) {
if (event == cv::EVENT_LBUTTONDOWN) {
cv::Point* p = static_cast<cv::Point*>(userdata);
p->x = x;
p->y = y;
}
}

int main() {
cv::Mat image = cv::imread("image.jpg");

// 创建窗口并显示图像
cv::namedWindow("Image");
cv::imshow("Image", image);

// 定义点的坐标
cv::Point point;

// 设置鼠标回调函数,并传递点的坐标作为用户数据
cv::setMouseCallback("Image", onMouse, &point);

// 等待鼠标点击
cv::waitKey(0);

// 输出点击点的坐标
std::cout << "Clicked point coordinates: (" << point.x << ", " << point.y << ")" << std::endl;

cv::destroyAllWindows();

return 0;
}

在上述示例中,我们首先读取一张图像并显示在窗口中。然后,我们定义了一个 cv::Point 对象 point 用于存储鼠标点击的坐标。

接下来,我们使用 cv::setMouseCallback() 函数设置鼠标点击事件的回调函数,即 onMouse 函数,并将 point 对象的地址作为用户数据传递给回调函数。

在回调函数 onMouse 中,我们检测是否发生了鼠标左键点击事件,并将点击的坐标信息存储在 point 对象中。

最后,我们使用 cv::waitKey(0) 等待鼠标点击事件,并在用户点击后输出点击点的坐标信息。

请注意,点击事件发生后,点击点的坐标将存储在 point 对象中,可以在其他地方访问和使用。

运行示例代码后,在图像窗口中点击鼠标左键,程序将输出点击点的坐标信息。

需要注意的是,在使用 OpenCV 的鼠标事件时,确保已经创建了窗口并调用了 cv::setMouseCallback() 函数来设置回调函数,并通过用户数据参数传递要保存坐标信息的对象的地址。

从内存读取图片数据

以下是使用 OpenCV C++ 从内存中读取图像数据并显示的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <opencv2/opencv.hpp>

int main() {
// 假设图像数据存储在内存中的缓冲区 image_data 中

// 图像参数
int width = 640;
int height = 480;
int channels = 3; // RGB 图像,通道数为 3

// 创建一个 cv::Mat 对象,将图像数据与缓冲区关联起来
cv::Mat image(height, width, CV_8UC3, image_data);

// 创建一个名为 "Image" 的窗口
cv::namedWindow("Image");

// 显示图像
cv::imshow("Image", image);

// 等待按下任意按键
cv::waitKey(0);

return 0;
}

在上述示例中,假设图像数据存储在内存中的 image_data 缓冲区中。我们使用图像的宽度、高度和通道数创建了一个 cv::Mat 对象 image,并将其与缓冲区关联起来。

然后,我们通过调用 cv::namedWindow() 函数创建了一个名为 “Image” 的窗口。接下来,使用 cv::imshow() 函数将 image 显示在窗口中。

最后,我们调用 cv::waitKey() 函数等待用户按下任意按键,以保持窗口打开状态。当用户按下按键后,程序将继续执行,并返回按键的 ASCII 值。

请注意,上述示例假设图像数据已经按正确的格式(像素顺序、颜色通道顺序等)存储在 image_data 缓冲区中。如果图像数据的格式不匹配,可能会导致图像显示不正确。确保以正确的格式存储图像数据非常重要。

此外,还可以根据实际需要对图像进行预处理,例如调整大小、转换颜色空间等操作。以上示例仅演示了从内存中读取图像数据并显示的基本操作,具体的图像处理和操作可以根据实际需求进行扩展。

请注意,以上信息基于 OpenCV 4.5.2 版本。不同的版本可能会略有差异,建议参考官方文档获取特定版本的详细信息。