图像运算、掩膜/ROI 与绘制交互

图像运算、掩膜/ROI 与绘制交互

目标:掌握「在图像上做代数/逻辑运算」和「局部处理」这两类日常操作,

会画标注、会做鼠标交互调试,并且知道 cv::putText 画不了中文这件事的真相。

写作基线:OpenCV 4.13.0。本篇的数值与配图均为本机实测。


一、算术运算:溢出不报错,这才是最危险的

1.1 一个问题

一张灰度图,你想让整体亮一点。最自然的写法是写个循环:

cpp 复制代码
cv::Mat img = ...;                     // CV_8UC1,像素值 0-255

for (int r = 0; r < img.rows; ++r) {
    uchar* p = img.ptr<uchar>(r);
    for (int c = 0; c < img.cols; ++c) {
        p[c] = p[c] + 100;               // ← 这里有个严重的 bug
    }
}

p[c] 是 unsigned char。当 p[c] 是 200 时会发生什么?

  1. C++ 的整数提升规则把 p[c] 提升为 int,加法在 int 上算,得到 300
  2. 把 300 赋回 unsigned char ------ 这是模 256 的截断,得到 44

这不是未定义行为,是标准明确规定的回绕。 也正因为它是"定义良好的",编译器和运行时都不会给你任何提示。

于是你的图不是变亮了,而是超过 255 的地方全部变成黑色。

⚠️ 顺带说明:cv::Mat 之间的 operator+(走 MatExpr)是饱和的 ,因为它内部调用了 cv::add。

所以「用运算符」在 C++ 里通常没问题。真正的危险都在手写循环 里 ------ 也就是上面这种写法。

本文因此不讨论「该不该用运算符」,只讲循环里的溢出。

1.2 实测对比

图 1:cv2.add 与 numpy 加法的区别(OpenCV 4.13.0 实测)。本机实测第 200 列像素:原图 200 | cv2.add 255 | numpy 44。

看中间那张和下面那张的差别 ------ 一个平滑地从白过渡到黑(正确),一个突然跳变成黑色(错误)。这个跳变就是溢出的痕迹。

1.3 正确做法:用 OpenCV 的运算函数

cpp 复制代码
cv::Mat img = ...;                     // CV_8UC1

cv::Mat brighter;
cv::add(img, 100, brighter);           // ✅ 超过 255 的部分饱和到 255

// 或者用标量
cv::add(img, cv::Scalar(100), brighter);

// 也可以用带缩放的加法(常用于加权叠加)
cv::addWeighted(a, 0.7, b, 0.3, 0.0, dst);   // dst = 0.7*a + 0.3*b + 0

cv::add 内部做了 saturate_cast ------ 超出范围的值被截断到边界,而不是回绕。这就是这些函数存在的理由。

1.4 运算函数对照表

用途 C++ 写法 等价数学
加 cv::add(a, b, dst) dst = a + b(饱和)
加权和 cv::addWeighted(a, α, b, β, γ, dst) dst = αa + βb + γ
减 cv::subtract(a, b, dst) dst = a - b(饱和到 0)
乘 cv::multiply(a, b, dst) dst = a * b
除 cv::divide(a, b, dst) dst = a / b
带缩放 cv::divide(a, b, dst, scale) dst = scale * a / b
绝对差 cv::absdiff(a, b, dst) `dst =
取反 cv::bitwise_not(a, dst) dst = ~a
逐元素最值 cv::max(a, b, dst) / cv::min(a, b, dst) 逐元素取最大/最小

cv::absdiff 在缺陷检测里极其常用 ------ 它是「两张图相减找差异」的标准工具,不会因为顺序不同而得到负值。

1.5 手动 saturate_cast

如果你确实要写循环,用 cv::saturate_cast<T>():

cpp 复制代码
for (int r = 0; r < img.rows; ++r) {
    uchar* p = img.ptr<uchar>(r);
    for (int c = 0; c < img.cols; ++c) {
        p[c] = cv::saturate_cast<uchar>(p[c] + 100);    // ✅ 安全
    }
}

注意 p[c] + 100 这个表达式本身 :p[c] 会先被提升为 int,加法在 int 上完成(不会溢出),然后 saturate_cast<uchar> 把它截断到 [0, 255]。

但如果两个 uchar 相加再乘,中间结果可能超出 int ------ 那种情况下要显式先转成 int 或 float 再算。


二、掩膜与 ROI:局部处理的两条路

2.1 别名的坑:cv::Rect 是 (x, y, w, h)

cpp 复制代码
cv::Mat roi = img(cv::Rect(10, 20, 30, 40));
//                        x   y   w   h

不是 (x1, y1, x2, y2)。写成后者会取到一个位置和大小都不对的区域 ------ 而且不报错。

2.2 掩膜:按「像素条件」选区域

cpp 复制代码
cv::Mat gray, mask, cut;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);

// 阈值生成掩膜:亮于 200 的像素为 255,其余为 0
cv::threshold(gray, mask, 200, 255, cv::THRESH_BINARY);

// 用掩膜取出原图对应像素,其余置 0
cv::bitwise_and(img, img, cut, mask);

实测:本篇测试图中掩膜命中了 6625 / 95000 个像素(7.0%) ,即按 > 200 筛出的亮区。

cv::bitwise_and(src, src, dst, mask) 这个「自己与自己做与运算」的写法看起来奇怪,但它是标准用法 ------ 效果就是「mask 为 0 的地方输出 0,其余保留原值」。

等价的写法是 img.copyTo(cut, mask) ------ 注意 copyTo 带 mask 时,是「只复制 mask 非零处」,目标的其他位置保持原样(不是清零)。两者语义不同:

cpp 复制代码
cv::Mat dst1 = cv::Mat::zeros(img.size(), img.type());
img.copyTo(dst1, mask);              // mask=0 处保持 dst1 的原值(这里是 0)

cv::Mat dst2;
cv::bitwise_and(img, img, dst2, mask);   // mask=0 处一定是 0

如果目标图不是全 0 初始化的,这两个写法结果不同。 这是很隐蔽的一类 bug。

2.3 ROI:按「矩形位置」选区域

cpp 复制代码
cv::Mat roi = img(cv::Rect(10, 20, 30, 40));     // 视图,共享数据
roi = cv::Scalar(0, 0, 255);                      // 直接改到原图上

或者用 copyTo 把一块贴回去:

cpp 复制代码
cv::Mat patch = MakePatch();                      // 尺寸必须匹配目标 ROI
patch.copyTo(img(cv::Rect(x, y, w, h)));

2.4 水印:ROI + addWeighted

这是 ROI 最典型的用法 ------ 只需要处理一小块区域,就不要对整个图做运算:

cpp 复制代码
// 做一个半透明的 logo
cv::Mat logo = cv::Mat::zeros(80, 160, CV_8UC3);
cv::circle(logo, cv::Point(40, 40), 32, cv::Scalar(255, 255, 255), cv::FILLED);
cv::putText(logo, "LOGO", cv::Point(52, 48),
            cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(255, 255, 255), 2);

// 定位到右下角
const int x = img.cols - 170;
const int y = img.rows - 90;

// 只对这块 ROI 做加权叠加
cv::Mat roi = img(cv::Rect(x, y, logo.cols, logo.rows));
cv::addWeighted(roi, 0.5, logo, 0.5, 0.0, roi);
//             ↑原图权重    ↑logo权重       ↑加到 ROI 上(视图,直接改原图)

实测中这块 ROI 是 160x80 @ (210,160) ------ 坐标是从图的实际尺寸算出来的,不是写死的。

如果写成 cv::addWeighted(img, ..., logo, ..., dst),会因为尺寸不匹配直接报错。用水印就必须走 ROI。

图 2:水印叠加、掩膜抠图、ROI 局部替换(OpenCV 4.13.0 实测)。第一格是原图,第二格是右下水印的效果。

实测数据 :水印 ROI 尺寸 160×80 @ (210, 160);掩膜(灰度 > 200 的亮区)命中 6625 / 95000 像素(7.0%)。

2.5 创建背景图

cpp 复制代码
cv::Mat canvas = cv::Mat::zeros(480, 640, CV_8UC3);          // 黑底
cv::Mat canvas2(480, 640, CV_8UC3, cv::Scalar(255, 255, 255)); // 白底

cv::Mat::zeros 是静态方法,尺寸在前;cv::Mat(rows, cols, type, value) 是构造函数,尺寸也是 (行, 列) ------ 注意和 cv::Rect 的 (x, y, w, h) 顺序不同,容易混。


三、绘制:往图上画东西

cpp 复制代码
// 直线
cv::line(img, cv::Point(10, 10), cv::Point(200, 200),
         cv::Scalar(0, 0, 255), 2, cv::LINE_AA);

// 矩形
cv::rectangle(img, cv::Rect(50, 50, 120, 80),
              cv::Scalar(0, 255, 0), 2);
cv::rectangle(img, cv::Point(50, 50), cv::Point(170, 130),
              cv::Scalar(0, 255, 0), 2);          // 也可以用两个对角点

// 圆
cv::circle(img, cv::Point(300, 200), 60,
           cv::Scalar(255, 0, 0), cv::FILLED);    // FILLED 或 -1 表示实心

// 椭圆
cv::ellipse(img, cv::Point(400, 300), cv::Size(80, 50),
            30.0,                              // 旋转角度
            0, 360,                            // 起始/结束角度
            cv::Scalar(0, 255, 255), 2);

// 多边形(折线)
std::vector<cv::Point> pts;
pts.push_back(cv::Point(100, 100));
pts.push_back(cv::Point(200, 120));
pts.push_back(cv::Point(150, 220));
cv::polylines(img, pts, /*isClosed=*/true, cv::Scalar(255, 255, 0), 2);

// 实心多边形
std::vector<std::vector<cv::Point> > polys;
polys.push_back(pts);
cv::fillPoly(img, polys, cv::Scalar(255, 255, 0));

// 文字(仅 ASCII,下一节详说)
cv::putText(img, "OK", cv::Point(20, 40),
            cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(255, 255, 255),
            2, cv::LINE_AA);

三个参数细节

thickness 为负(或 cv::FILLED = -1)表示填充。

lineType 用 cv::LINE_AA 开抗锯齿。 默认的 LINE_8 画斜线和圆会有明显锯齿。代价是慢一些 ------ 批量绘制大量图元时可以考虑关掉。

cv::polylines 和 cv::fillPoly 的参数形式不同:

  • polylines 接受 std::vector<Point>(一条折线)
  • fillPoly 接受 std::vector<std::vector<Point>>(多条多边形)

传错了会编译失败,不是运行时问题。

测量文字尺寸

cpp 复制代码
int baseline = 0;
const cv::Size sz = cv::getTextSize("Result: PASS",
                                    cv::FONT_HERSHEY_SIMPLEX, 1.0, 2,
                                    &baseline);

getTextSize 返回文字的包围盒,baseline 是基线到包围盒底部的距离。要在图上居中或者做背景框时会用到。

注意它只对 ASCII 有意义 ------ 同一个字符串如果含中文,putText 画出来的和 getTextSize 算出来的完全对不上(下一节)。


四、cv::putText 画不了中文:真相与解法

这是中文读者必然撞上、而英文教程完全不会提的问题。

4.1 现象

图 3:cv::putText 画中文的真实结果(OpenCV 4.13.0 实测)。"检测结果:合格" 这 7 个字(UTF-8 共 21 字节)被画成了 21 个问号。

同一个 putText 调用,画英文完全正常,画中文变成一问号串。

实测数据 :字符串 "检测结果:合格" 有 7 个字符 ,UTF-8 编码是 21 字节 ,画出来是 21 个问号。

4.2 为什么

cv::putText 硬编码使用 OpenCV 内置的 Hershey 矢量字体。这是 1967 年 Allen Hershey 为绘图仪设计的一套字体,OpenCV 内置了它的约 95 个 ASCII 字形。

它里面一个 CJK 字形都没有。

而 putText 遇到无法映射的字符时,会画一个 ?。由于它按字节遍历字符串,每个非 ASCII 的 UTF-8 字节都会变成一个 ? ------ 这就是为什么 21 字节变成了 21 个问号(而不是 7 个)。

4.3 两个常见的错误尝试

错误尝试 1:换个 FONT_HERSHEY_* 变体。

cpp 复制代码
cv::putText(img, "检测结果", pt, cv::FONT_HERSHEY_DUPLEX, 1.0, color);
// 结果一样是问号

没用。 所有 FONT_HERSHEY_* 变体都是 Hershey 字体族的不同字重/风格,全都没有 CJK 字形。

错误尝试 2:转换编码。

cpp 复制代码
cv::putText(img, "检测结果", pt, cv::FONT_HERSHEY_SIMPLEX, 1.0, color);
// 换成 GBK、UTF-16......都一样是问号

也没用。 问题不在编码,在光栅化器里根本没有那些字形 。Python 侧用 u8"中文" 或 .encode('utf-8') 同样无效。

顺便澄清一个流传很广的说法 :即使你的 OpenCV 编译时开了 -DWITH_FREETYPE=ON,cv::putText 依然走 Hershey 路径 。FreeType 支持并没有暴露给原生 putText API ------ 它只给了 contrib 里的 cv::freetype 模块。很多中文教程在这一点上写错了。

4.4 正确做法

方案 A:contrib 的 freetype 模块(需要 contrib 和 FreeType 库)

cpp 复制代码
#include <opencv2/freetype.hpp>

cv::Ptr<cv::freetype::FreeType2> ft2 = cv::freetype::createFreeType2();
ft2->loadFontData("C:/Windows/Fonts/msyh.ttc", 0);      // 微软雅黑

ft2->putText(img, u8"检测结果:合格",
             cv::Point(10, 40),
             28,                                     // 字号
             cv::Scalar(0, 0, 255),
             cv::FILLED,                             // 负值/填充表示实心字形
             cv::LINE_AA,
             true);                                  // bottomLeftOrigin

代价:需要自编译带 contrib 的 OpenCV(见 00-02 的 vcpkg 路线),部署时还要带上 FreeType 的 DLL。

方案 B:在图上只写 ASCII,中文交给外部 UI(推荐用于生产)

把中文放在窗口标题、控制台、或者宿主程序的界面元素里,图上只画英文和数字。

对于 NX 插件这类已经有 UI 框架的场景,这是最省事的做法 ------ 你本来就有 NXMessageBox,没必要非把中文画在图里。

方案 C:用别的库渲染后再合成

PIL(Python)、GDI/DirectWrite(Windows)、Qt 都可以。思路都是「先渲染成一张图,再把图贴到 cv::Mat 上」。本系列的配图就是这么做的 ------ tools/figures/_common.py 里的 load_cjk_font() 加载微软雅黑,全部中文标注走 PIL。

如果只是自己调试看效果,Python + PIL 是最快的:

python 复制代码
from PIL import Image, ImageDraw, ImageFont
import numpy as np, cv2

pil = Image.fromarray(cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB))
ImageDraw.Draw(pil).text((10, 40), "检测结果:合格",
                         font=ImageFont.truetype("C:/Windows/Fonts/msyh.ttc", 28),
                         fill=(0, 0, 255))
bgr = cv2.cvtColor(np.array(pil), cv2.COLOR_RGB2BGR)

注意 ImageDraw.text 的坐标是左上角 ,而 cv::putText 默认是左下角(基线位置)。换算时容易差一个文字高度。


五、鼠标交互

调试时经常需要「点一下看看这个位置的像素值是多少」。

cpp 复制代码
struct MouseState {
    cv::Point pt;
    bool      pressed;
    MouseState() : pt(-1, -1), pressed(false) {}
};

static MouseState g_state;
static cv::Mat    g_img;

void OnMouse(int event, int x, int y, int flags, void* userdata)
{
    MouseState* st = static_cast<MouseState*>(userdata);

    switch (event) {
    case cv::EVENT_LBUTTONDOWN:
        st->pressed = true;
        st->pt = cv::Point(x, y);
        std::cout << "按下 (" << x << ", " << y << ")" << std::endl;

        // 打印该点的像素值
        if (x >= 0 && x < g_img.cols && y >= 0 && y < g_img.rows) {
            const cv::Vec3b px = g_img.at<cv::Vec3b>(y, x);
            std::cout << "  BGR = (" << static_cast<int>(px[0]) << ", "
                      << static_cast<int>(px[1]) << ", "
                      << static_cast<int>(px[2]) << ")" << std::endl;
        }
        break;

    case cv::EVENT_MOUSEMOVE:
        if (st->pressed) {
            st->pt = cv::Point(x, y);
        }
        break;

    case cv::EVENT_LBUTTONUP:
        st->pressed = false;
        std::cout << "释放 (" << x << ", " << y << ")" << std::endl;
        break;

    default:
        break;                          // 必须有 default
    }
}

int main()
{
    g_img = cv::imread("test.png");
    if (g_img.empty()) { return -1; }

    cv::namedWindow("win");
    cv::setMouseCallback("win", OnMouse, &g_state);
    cv::imshow("win", g_img);
    cv::waitKey(0);
    return 0;
}

实时框选 ROI 的常见模式是:按下时记起点,拖动时用 imshow 刷一张带临时矩形的副本,松开时确定最终矩形。注意不要直接画在原图上 ------ 每次刷新都会留下上一次的框。正确做法是保留一份原图副本,每次在副本上重新画:

cpp 复制代码
cv::Mat display = g_img.clone();              // 每次刷新都从这份干净的副本开始
cv::rectangle(display, rect, cv::Scalar(0, 255, 0), 2);
cv::imshow("win", display);

六、这套方案什么时候会失败

  • 整数图像上做加减乘。 溢出会回绕且不报错。一律用 cv::add / cv::subtract / cv::multiply / cv::absdiff。
  • cv::Rect 超出图像边界。 OpenCV 会抛出异常(在 Release 下也可能只是裁剪)。构造前应该做范围检查,尤其是坐标来自外部输入时。
  • copyTo 带掩膜时目标图未初始化。 掩膜为 0 的位置保持目标的原值 ------ 如果目标里有垃圾数据,结果里就有垃圾数据。用 bitwise_and 或者先把目标清零。
  • cv::putText 画任何非 ASCII 字符。 不只是中文,日文、韩文、俄文、甚至带重音的拉丁字母全都会变成问号。
  • getTextSize 和 putText 的坐标基准不一致。 putText 的 org 是文字左下角 (基线),getTextSize 返回的矩形也是相对基线算的。想精确排版要自己减 sz.height。
  • 在 imshow 的原图上反复画标注。 标注会累积。始终保留一份干净的副本。
  • 用 ROI 视图做长时间存活的对象。 前面说过,ROI 持有父图的整块内存。

七、Python 对照

7.1 算术运算:Python 更容易写出错而不自知

Python 侧的危险在于:numpy 的加法语法上完全合法,运算也照常返回,只是结果不是你要的。

以下是在本机 NumPy 2.4.3 上的实测:

python 复制代码
import numpy as np, cv2

img = np.array([[200]], dtype=np.uint8)

print(img + 100)                # [[ 44]]   ← 回绕!不是 300
print(img + np.uint8(100))      # [[ 44]]   ← 回绕
print(cv2.add(img, 100))        # [[255]]   ← 饱和,正确

三种写法里有两种给出的是错误结果 ,而且一个警告都不会有。

关于 NumPy 的版本差异 :NumPy 1.x 时代,uint8 数组 + Python int 会提升到能容纳结果的类型(得到 300);NumPy 2.0 起收紧了类型提升规则 (NEP 50),Python 的 int 在运算中被视为"弱类型",结果会保持 uint8 ------ 于是变成回绕。

也就是说:同一行 Python 代码,在 NumPy 1.x 和 2.x 上结果不同。 这正是 5.0 迁移里「NumPy 2.x 支持」被列为一项变更的原因。

所以不要靠记忆去区分哪种写法安全。 只要记住一条:

图像像素运算一律用 cv2.add / cv2.subtract / cv2.multiply / cv2.absdiff,不要用 numpy 的 + - * /。

多通道情况也一样(实测):

python 复制代码
c = np.array([[[200, 200, 200]]], dtype=np.uint8)
c + np.uint8(100)      # -> [[44, 44, 44]]    三个通道一起回绕
cv2.add(c, 100)        # -> [[255, 255, 255]] 三个通道一起饱和

7.2 绘制函数的差异

python 复制代码
# C++: cv::rectangle(img, cv::Rect(50, 50, 120, 80), color, 2);
# Python 用两个点:
cv2.rectangle(img, (50, 50), (170, 130), (0, 255, 0), 2)
#               ↑pt1      ↑pt2 = pt1 + (w, h)

# C++ 的 cv::Rect 在 Python 里没有直接对应,用 (x, y, w, h) 元组要自己转成两个点
x, y, w, h = 50, 50, 120, 80
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)

这是很容易出错的一处 :C++ 习惯用 Rect,Python 的绘图函数收的是两个点。直接把 (50, 50, 120, 80) 传给 Python 的 rectangle 会被当成 pt1=(50,50), pt2=120 ------ 参数个数不对,报错还好;要是参数个数刚好对上是更危险的。

常用等价对照:

功能 C++ Python
矩形 cv::rectangle(img, cv::Rect(x,y,w,h), c, t) cv2.rectangle(img, (x,y), (x+w,y+h), c, t)
圆 cv::circle(img, cv::Point(x,y), r, c, t) cv2.circle(img, (x,y), r, c, t)
填充 cv::FILLED 或 -1 cv2.FILLED 或 -1

(C++ 的 cv::Rect 在 Python 侧对应的是 cv2.boundingRect 等的返回值,是 (x, y, w, h) 元组,可以解包。)

7.3 掩膜参数的位置

python 复制代码
# C++ 是位置参数:cv::bitwise_and(a, b, dst, mask)
# Python 是关键字参数:
cv2.bitwise_and(a, b, mask=mask)

Python 侧 bitwise_and 必须用 mask= 关键字。 写成位置参数会报错。

7.4 putText 的中文

Python 侧完全一样画不了中文,原因相同(同一个 Hershey 字体)。备选方案:

python 复制代码
# 方案 1:PIL(推荐)
from PIL import Image, ImageDraw, ImageFont
pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
ImageDraw.Draw(pil).text((10, 40), "检测结果:合格",
                         font=ImageFont.truetype("C:/Windows/Fonts/msyh.ttc", 28),
                         fill=(0, 0, 255))
img = cv2.cvtColor(np.array(pil), cv2.COLOR_RGB2BGR)

# 方案 2:opencv-contrib-python 里的 freetype
# (需要先 pip install opencv-contrib-python,
#   注意不能和 opencv-python 同时装)

# 方案 3:图上只写 ASCII,中文走窗口标题或外部 UI

注意 PIL 的坐标是左上角,cv2.putText 是左下角基线 ------ 两者换用时坐标要调整。


八、自测

  1. unsigned char 的值是 200,执行 p[c] = p[c] + 100; 之后 p[c] 是多少?为什么?
  2. img.copyTo(dst, mask) 和 cv::bitwise_and(img, img, dst, mask) 在什么情况下结果不同?
  3. 为什么 cv::putText 画中文会变成一串问号?换成 FONT_HERSHEY_DUPLEX 能解决吗?
  4. cv::putText 的 org 参数指的是文字的哪个位置?

答案

  1. 44 。200 + 100 = 300,unsigned char 只能存 0-255,300 溢出后回绕成 300 - 256 = 44。要避免就包一层 cv::saturate_cast<uchar>(...),或者直接用 cv::add。
  2. 当 dst 不是全 0 初始化时。 copyTo 带掩膜是「只复制掩膜非零处,其余位置保持 dst 原值」;bitwise_and 是「掩膜为 0 处一定输出 0」。如果 dst 里有旧数据,两者结果不同。
  3. 因为 putText 用的内置 Hershey 字体里根本没有 CJK 字形 ,它把每个非 ASCII 的 UTF-8 字节都画成一个 ?。换成任何 FONT_HERSHEY_* 变体都没用 ------ 它们都是同一个字体族。
  4. 文字的左下角(基线位置)。这也是为什么经常要把 y 坐标加上文字高度才能让文字贴合框的上沿。

阶段 1 完成

到这里,OpenCV 的基础部分就走完了。你现在应该能:

  • 理解 cv::Mat 的指针语义,知道什么时候会共享数据、什么时候需要 clone()
  • 在 Windows 上正确处理中文路径的图像读写
  • 用 HSV 做颜色筛选,用直方图判断图像质量
  • 写不会溢出的像素运算代码,用掩膜和 ROI 做局部处理
  • 知道 cv::putText 画不了中文,以及正确的替代路径
相关推荐
UIU1141 小时前
P1028 [NOIP 2001 普及组] 数的计算
c++·算法
Keven_112 小时前
BUG:UVA116
算法·bug·图论
Henry-SAP2 小时前
Beam大模型开源引爆AI热潮
算法
知识分享小能手2 小时前
C学习教程,从入门到精通,C语言概述 —— 知识点详解(1)
c语言·开发语言·学习
东木月2 小时前
Windows C 盘清理指南:安全删除临时文件、缓存与 Prefetch
c语言·windows·安全
垆边人似月.2 小时前
日志时间窗口内的最大并发请求数(100分 / 滑动窗口 + 排序)
数据结构·c++·算法
Nil2082 小时前
leetcode 70爬楼梯
算法·leetcode·动态规划
Keep_Trying_Go2 小时前
MobileSAM 论文方法详解
人工智能·深度学习·计算机视觉·分割算法
垆边人似月.2 小时前
最长递增子序列的个数(200分 / 动态规划)
算法·动态规划