OpenCV Mat统计小于0个数:类型、连续性与高效实现

看到这个标题——opencv mat 统计小于0的个数——你可能觉得这就是个遍历计数的小问题。我一开始也这么认为,结果真上手的时候,被 Mat 的类型、通道数、连续性轮番招呼了一遍。OpenCV 的 Mat 不是一个简单的一维数组,它背后有内存布局、数据类型、通道等一堆概念,任何一个理解错了,统计出来的数都可能离谱。

这个需求其实很常见。举几个我碰过的场景:深度传感器输出的深度图里,无效点经常用负值标记,统计负值个数就是评估一帧深度图像质量的快捷方式;两张图像做差分后,残差矩阵里的负值代表某一个方向的变化量,统计它能判断场景中是否有遮挡或光照突变;训练回归模型做图像重建时,预测值与真值相减得到误差图,负误差的比例会影响后续的调参策略。在这些场景里,一个可靠的“负值计数”往往只是第一步,但如果不把它写对,后面的逻辑全都白搭。

这篇文章会把这个问题拆开聊:先从 Mat 的内存结构讲起,再给四套不同侧重点的实现代码,最后整理我在实操中踩过的坑和排查思路。不管你是刚接触 OpenCV 的初学者,还是写了多年 C++ 但一碰到 Mat 就头疼的老手,都能在里头找到一段能直接拿去用的代码。

1. 需求拆解:为什么一个计数问题能写出这么多花样

1.1 实际场景决定统计口径

很多人看到“统计小于0的个数”,第一反应就是“数呗”。但真正做工程时,先要搞清楚一件事:你数的到底是“像素”还是“通道值”?

比如一张 CV_32FC3 的三通道浮点图,一个像素位置上有三个 float。如果按“像素数”统计,那一个像素只要有任意一个通道小于0,要不要算一次?如果三个通道都小于0,是算一次还是算三次?如果我关心的是第二个通道(比如某个算法输出的深度分量),那就只该统计那个通道。

我见过不少同事直接把代码写成m.at<float>(r, c) < 0,然后对着结果百思不得其解。原因很简单:at<float>对一个三通道 Mat 来说,取到的只是第一个通道的值,统计结果天然少了一大半。

所以在写任何代码之前,先问自己三个问题:

  • 这个 Mat 是什么类型,有没有符号?
  • 它是单通道还是多通道?
  • 我要统计的“一个数”,是像素级口径,还是通道值级口径?

这三个问题不解决,后面那些花哨的优化全是空中楼阁。

1.2 先认识 Mat 的内存布局

OpenCV 的 Mat 由“头部”和“数据指针”组成。头部记录 rows、cols、channels、depth、step 这些元信息,数据指针data指向真正存数值的内存区域。

关键概念有三个:

  • depth:每个数值的位深和格式,比如 CV_8U、CV_16S、CV_32F、CV_64F。
  • channels:每个像素有几个通道,常见 C1、C2、C3、C4。
  • step:每一行数据在内存里占用的字节数,这个很重要。

你可能会觉得:Mat 不就是一个二维数组吗?一行一行连续排着呗。大多数情况是这样,但有一种例外:从一个大矩阵里用 Rect 抠出来的 ROI。这种子矩阵的数据仍然是原矩阵内存的一部分,行与行之间会隔着原矩阵的其他行数据,所以在内存里并不连续。

isContinuous()这个函数就是用来判断数据是否从头到尾连成一片的。如果它返回 true,你可以把 Mat 当成一个一维数组来访问;如果返回 false,你还按一维数组访问,读到的就不是你想要的顺序,甚至会越界。

另外注意total()的返回值。对单通道矩阵来说,它等于 rows × cols;对 CV_32FC3 这种三通道矩阵,它返回的是 rows × cols × 3,也就是说它把每个通道值都算作一个“元素”。很多人在指针遍历时用 total() 做循环上界,发现结果“莫名其妙多算了两倍”,就是因为没搞清楚这个细节。

用生活里的例子来类比:一个矩阵就像一摞外卖单,正常情况下每张纸都紧挨着,你可以从头翻到尾。但如果是从一沓大纸上裁下来的几张小条,中间就可能隔着很多空白区,直接顺着页码翻就会串行。Mat 的 isContinuous 就是告诉你,面前这摞纸到底能不能从头一路翻到尾。

1.3 几种主流方案的取舍

统计小于0的个数,我在不同项目里用过四类写法,各有优劣:

方案优点缺点适用场景
at<T>(r,c) 双重循环可读性最好,新手一眼看懂慢,尤其大图和视频流场景小矩阵、学习演示
compare + countNonZero内部有 SIMD 和线程优化,代码短需要额外分配一个 mask 矩阵绝大多数工程场景首选
手写指针遍历不额外分配内存,可控性最强代码稍长,要自己处理类型和连续性嵌入式、内存紧张环境
parallel_for_ 并行分段能利用多核,大矩阵收益明显实现最复杂,线程调度有开销上百万元素的大矩阵、实时处理

有人会问:为什么不直接用threshold?因为 threshold 的原生语义是“把满足条件的像素设为一个固定值”,比如THRESH_BINARY_INV会把小于等于阈值的像素设为最大值,它会把等于0的情况也算进去,用来数“严格小于0”不准确。还有人想用minMaxLoc找最小值,判断最小值小于0就知道“存在负数”,但那只能回答“有没有”,回答不了“有几个”。

所以先别急着写循环,选对工具往往比写对代码更省力。

2. 动手前必须搞清楚的三个细节

2.1 类型匹配:无符号数据根本没有“负数”

这是我最先踩的坑。某次我从上游模块拿到一个 Mat,看起来是深度图,实际类型是 CV_8U。CV_8U 是 8 位无符号整数,取值范围 0 到 255,它从定义上就“没有负数”。我写了半天遍历,count 永远是 0,后来打印类型才发现问题根源不在遍历逻辑,而在数据本身。

所以写代码之前,先在关键位置打印一行:

std::cout << "depth=" << m.depth() << " channels=" << m.channels() << " total=" << m.total() << " continuous=" << m.isContinuous() << std::endl;

OpenCV 里 depth 是一个数字编号,常见对应关系:CV_8U=0,CV_8S=1,CV_16U=2,CV_16S=3,CV_32S=4,CV_32F=5,CV_64F=6。如果你想统计小于0的个数,只要 depth 是无符号类型——CV_8U、CV_16U——那结果一定是 0,不用费劲遍历。

如果数据是 CV_16S、CV_32S 这类有符号整型,或者 CV_32F、CV_64F 浮点型,才有统计负数的必要。遇到类型不对,可以做一次convertTo,比如把 CV_16S 转成 CV_32F 再统计。但要注意,浮点转换对很大的整数可能引入精度损失,如果数据范围本身不夸张,一般没问题。

我的习惯是在函数开头加一个断言:

CV_Assert(!m.empty()); CV_Assert(m.type() == CV_32FC1 || m.type() == CV_64FC1);

这样类型不匹配时马上崩,而不是带着错误的类型算出一个“看起来合理”的数字。

2.2 单通道和多通道:统计对象先定清楚

单通道的情况最简单,比如 CV_32FC1,一个像素就是一个 float,统计小于0就是数这些 float。

多通道的情况要小心。以 CV_32FC3 为例,每个像素有通道0、通道1、通道2三个值。统计时至少有三种口径:

  • 统计所有通道值中小于0的个数:相当于把三个通道拼接成一个长数组,逐个判断。
  • 统计指定通道(比如通道1)小于0的像素数:只关心某一维数据。
  • 统计“整像素三个通道都小于0”的个数:这是像素级判断,不是通道值级判断。

如果你没想清楚就写at<float>(r, c),那默认只取了通道0,另外两个通道全被忽略。如果你用指针按下标一个个数,又可能把三个通道全算进去,跟预期又不一样。

实际工程里处理多通道的话,我推荐两种做法:

std::vector<cv::Mat> channels; cv::split(m, channels); int count0 = cv::countNonZero(channels[0] < 0);

或者干脆在函数入口断言单通道,逼着调用方先想清楚数据维度。

2.3 空矩阵、子矩阵和连续性陷阱

空矩阵是所有遍历操作的头号大敌。m.empty()为 true 时,rows 和 cols 都是 0,直接访问ptr可能崩,total()返回 0 也可能让逻辑走偏。统计类函数开头的第一行,永远应该是判断是否为空。

子矩阵的坑更容易忽视。假设原图是 4000 × 3000,你用bigImg(cv::Rect(100, 100, 500, 500))截了一块 ROI,这块 ROI 的数据在内存里并不是 500 行连续排列的:每行开头在原图某个位置,行尾到下一行开头之间,隔着原图的“剩余宽度”数据。此时isContinuous()返回 false,如果你把它当成一维数组直接遍历total()个元素,读到的后半部分全是错的。

对付这种情况有两种选择:

  • 调clone()把 ROI 复制成一块独立连续内存,代价是额外一次拷贝。
  • 不 clone,按“行指针 + 列数”的方式遍历,代码稍微多几行,但零拷贝。

我的建议是,如果这个 ROI 接下来还要反复用很多次,可以 clone 一次;如果只是统计一下数量,直接按行遍历,省掉拷贝开销。

3. 四套完整实现:从“能跑”到“跑得快”

3.1 最直观但最慢的 at 遍历

初学阶段最容易写出这样的代码:

int countNegativeAt(const cv::Mat& m) { int count = 0; for (int r = 0; r < m.rows; ++r) { for (int c = 0; c < m.cols; ++c) { if (m.at<float>(r, c) < 0.0f) ++count; } } return count; }

这段代码没毛病,逻辑清晰,小矩阵上跑得也很快。但到了大分辨率或者实时视频流里,性能就开始吃亏了。at需要在运行时根据类型和行列号计算偏移地址,虽然 release 版本下不做边界检查,但每一层都多一次函数调用和索引换算,累积起来相当可观。

我在一台普通笔记本上测过一个 1920×1080 的单通道 float 矩阵,这样写一轮大概要 20 多毫秒,对于一帧 60 毫秒预算的视频处理来说,光是数个数就占掉三分之一,属实不划算。

所以我的态度是:at 遍历适合教学和验证思路,不适合作为生产代码。

3.2 工程首选:compare + countNonZero

如果你只需要一个总数,OpenCV 早就帮你把路铺好了:

int countNegativeCompare(const cv::Mat& m) { cv::Mat mask; cv::compare(m, cv::Scalar(0.0), mask, cv::CMP_LT); int count = cv::countNonZero(mask); return count; }

这段代码的逻辑很好理解:compare把 m 和标量 0 逐元素比较,满足“小于”的像素在 mask 里写成 255,不满足的写成 0。mask 的类型是 CV_8U。接着countNonZero数一下 mask 里有几个非零点,因为 255 也是非零,所以数量正好等于小于0的个数。

这里要注意一个细节:mask 的值是 255 而不是 1。如果你后面想对 mask 做求和,记得除以 255;如果用countNonZero,因为只关心“是不是非零”,255 和 1 没有区别,直接数就行。

compare + countNonZero的组合之所以好用,是因为这两个函数在 OpenCV 内部都做了大量优化,底层线程库和 SIMD 指令会自己调度。你写的是一行代码,实际跑起来可能是多线程并行处理的。而且代码短,可维护性好,是工程里的默认选择。

唯一需要注意的是它会产生一个跟原图一样大的 mask 矩阵,额外占用一份内存。对一张 4K 单通道 float 图来说,原图约 33MB,mask 约 8MB,一般场景完全可以接受。如果连这 8MB 都敏感,那就看下一节的手写指针方案。

3.3 手写指针遍历与连续性优化

在嵌入式设备上,内存和性能都要抠着用,我一般写这样的函数:

int countNegativePtr(const cv::Mat& m) { CV_Assert(!m.empty()); CV_Assert(m.type() == CV_32FC1); int count = 0; if (m.isContinuous()) { const float* p = m.ptr<float>(0); size_t total = m.total(); for (size_t i = 0; i < total; ++i) { if (p[i] < 0.0f) ++count; } } else { for (int r = 0; r < m.rows; ++r) { const float* p = m.ptr<float>(r); for (int c = 0; c < m.cols; ++c) { if (p[c] < 0.0f) ++count; } } } return count; }

这段代码和 at 版本最大的区别是:直接用ptr<float>拿到行首指针,然后通过数组下标访问,省掉了at的索引换算。对于连续矩阵,一次拿到整块数据的起始指针,按total()个 float 从头数到尾,循环层数从嵌套变单层,编译器更容易做向量化优化。

isContinuous()分支是为了处理 ROI 子矩阵。连续时走一维循环,不连续时老老实实一行行取指针。你可以留意到不连续分支和 at 版长得挺像,但每行只调用一次ptr<float>,后面是连续内存里的数组下标访问,速度依然比at快一大截。

这个版本没有额外分配,内存占用最小。我经常在分析离线数据、处理大矩阵时用它。

3.4 并行分段:处理大矩阵的进阶方案

矩阵大到一定程度,单核遍历就浪费资源了。比如处理几千万个元素的浮点矩阵,哪怕每元素操作再简单,单线程也要几十毫秒。这时候可以上parallel_for_做行级并行:

int countNegativeParallel(const cv::Mat& m) { CV_Assert(!m.empty()); CV_Assert(m.type() == CV_32FC1); std::vector<int> partial(m.rows, 0); cv::parallel_for_(cv::Range(0, m.rows), [&](const cv::Range& range) { for (int r = range.start; r < range.end; ++r) { const float* p = m.ptr<float>(r); int cnt = 0; for (int c = 0; c < m.cols; ++c) { if (p[c] < 0.0f) ++cnt; } partial[r] = cnt; } }); return std::accumulate(partial.begin(), partial.end(), 0); }

这个版本把行号区间分给多个线程,每个线程只处理自己负责的那几行,结果写到partial[r]。因为每个线程写的数组槽位互不重叠,所以不需要加锁,最后在主线程里把 partial 累加一遍就行。

要说明的是,并行不是免费的。线程调度本身有开销,如果矩阵只有几百行几千列,并行版的耗时可能反而比单线程版更长。我的经验是,至少百万级别元素以上再考虑并行,不然收益不抵调度成本。

另外,OpenCV 底层如果编译了线程池,compare本身也可能是并行的。所以我的建议顺序是:先上 compare,真的不够再上自己写的并行版。

3.5 Python 版本的简洁写法

如果你用的是 Python 的 OpenCV 接口,底层矩阵其实是 numpy 数组,统计负数可以直接用 numpy,也可以走 OpenCV API:

import cv2 import numpy as np # 方法1:numpy 布尔比较 + 求和,最直观 cnt = int(np.sum(mat < 0)) # 方法2:OpenCV 风格,compare 生成 mask 再 countNonZero mask = cv2.compare(mat, 0, cv2.CMP_LT) cnt = cv2.countNonZero(mask)

两种方式结果一致。numpy 版本在 Python 里更常用,因为mat < 0会生成一个布尔数组,np.sum对它求和就是 True 的个数,语义跟“小于0的个数”完全对应。

如果说有什么要注意的,那就是cv2.countNonZero的输入在多通道情况下不同版本表现不太一致,稳妥做法是先cv2.split再分别统计;而np.sum(mat < 0)会把所有通道值都算进去。这跟前面说的“统计口径”是一回事,Python 只是换了个写法,该想清楚的还是得想清楚。

4. 从“小于0”扩展到任意统计需求

4.1 阈值区间与多条件组合

统计小于0只是起点。实际需求往往是“统计负多少个”、“统计在-100到0之间有多少个”这类带条件的计数。

在 C++ 里可以组合两个 compare 出来的 mask:

cv::Mat maskLow, maskHigh, maskBoth; cv::compare(m, cv::Scalar(-100.0), maskLow, cv::CMP_GT); // m > -100 cv::compare(m, cv::Scalar(0.0), maskHigh, cv::CMP_LT); // m < 0 cv::bitwise_and(maskLow, maskHigh, maskBoth); int count = cv::countNonZero(maskBoth);

这里统计的是同时满足“大于-100”和“小于0”的元素个数,也就是区间 (-100, 0)。两个 mask 都是 0/255 的二值图,bitwise_and会对每一位做与操作,只有两个条件都成立的位置才保留 255。

如果你想统计“小于-100 或 大于100”这种或条件,就把bitwise_and换成bitwise_or。

Python 版本一行就能写完:

cnt = int(np.sum((mat > -100) & (mat < 0)))

numpy 的布尔数组可以自由组合&、|、~,在离线分析场景下非常顺手。

4.2 按行/按列统计负数分布

有时候你不仅想知道总共有多少个负数,还想知道每一行、每一列分别有多少个。这种分布信息对定位问题区域特别有用,比如深度图里哪一行无效点多,或者残差图里哪一列偏差集中。

思路还是先得到 mask,再把它转成 0/1 的浮点矩阵,用reduce做求和:

cv::Mat mask; cv::compare(m, cv::Scalar(0.0), mask, cv::CMP_LT); cv::Mat maskF; mask.convertTo(maskF, CV_32F, 1.0 / 255.0); cv::Mat rowCount; // rows x 1,每行负数个数 cv::Mat colCount; // 1 x cols,每列负数个数 cv::reduce(maskF, rowCount, 1, cv::REDUCE_SUM); cv::reduce(maskF, colCount, 0, cv::REDUCE_SUM);

reduce的第二个参数是 int 类型,0 表示按列方向求和,输出每行一个结果;1 表示按行方向求和,输出每列一个结果。注意这里不能直接用 0/255 的 mask 做 reduce,否则每行结果会是“255 × 负数个数”。先除以 255 变成 0/1,reduce 出来的数值就直接是计数。

用 Python 做这个事更简单,np.sum(mask, axis=1)一行搞定。

4.3 计算异常占比

统计出个数后,很多场景还需要算一个比例。比如深度图无效像素占比:

double percent = 100.0 * count / (double)m.total();

这里要特别提醒:total()对单通道矩阵就是像素数,对多通道矩阵是像素数 × 通道数。如果你的统计口径是“通道值级”,分母用 total() 没问题;如果统计口径是“像素级”,分母应该用m.cols * m.rows。

这个占比在工程里很实用。我做某类图像质量评估时,先把无效像素占比算出来,一旦超过阈值就触发重新采集或告警。一个简单的数字,配合阈值判断,比肉眼盯着图像可靠得多。

5. 实战排查:我踩过的坑和速查表

5.1 典型错误表格

我把实际开发中遇到过的典型问题整理成了速查表,遇到类似现象可以直接对号入座:

现象可能原因解决办法
count 恒为 0depth 是 CV_8U / CV_16U 无符号类型打印 depth 确认,必要时先 convertTo
count 比预期大很多多通道 Mat 把所有通道都算了一遍先 split 或明确只统计指定通道
编译报错或取出乱码用 at 访问 CV_64FC1 矩阵用 at ,或统一转 CV_32F
用 threshold 统计多了THRESH_BINARY_INV 把等于0也算进去了改用 compare + countNonZero
遍历越界或结果错乱ROI 不连续却当一维数组遍历先判断 isContinuous,不连续时按行遍历
数据包含 NaNNaN 比较小于0永远为 false,被静默忽略需要专门分类处理 NaN 时,先 patchNaNs

表格里最后一行值得多说两句。浮点 Mat 里如果混入了 NaN,NaN < 0这个比较的结果永远是 false,所以它不会被计入“负数”。如果你统计的是“异常无效值”,而 NaN 恰恰也是异常之一,那你需要先处理 NaN,比如用cv::patchNaNs把它替换成某个固定值,再统一统计。

还有一个冷门细节:负零。IEEE 浮点标准里有 -0.0,它在比较时等于 0,所以-0.0 < 0也是 false。大多数业务场景不会在意这零点几的差异,但如果你的数据从数值计算里来,要清楚这一点。

5.2 性能对比:一个 1080p 实例

我在一台普通笔记本上,用 Release 模式测过 1920×1080 单通道 CV_32FC1 矩阵的几种实现,量级大致如下:

实现耗时量级额外内存备注
at 双重循环15 ~ 30 ms无Debug 模式会更慢
compare + countNonZero0.2 ~ 1 msmask 约 2MB工程首选
手写指针遍历0.5 ~ 1.5 ms无可控性最好
parallel_for_ 并行0.2 ~ 0.8 ms少量 partial 数组大矩阵才划算

这个表只是量级参考,不同 CPU、不同编译器、不同 OpenCV 版本都会有很大的差异。但一个结论是稳定的:at 版本跟其他方案完全不在一个数量级。如果你的代码里有一个“统计 Mat 负数个数”的函数卡在 20 毫秒,别怀疑,几乎一定是遍历方式的问题。

我也经常看到有人习惯性 Debug 模式测性能,数据惨不忍睹。记住,高性能测试永远在 Release 模式下做。

5.3 工程里的取舍建议

结合这些年的经验,我给自己定了一条规矩:能用 OpenCV 内置 API 的,绝不自己造循环。不是因为内置 API 一定更快,而是它把边界情况都处理好了,代码短,出错概率低,排查起来也省心。

如果你的需求就是一个“总数”,compare + countNonZero 永远是第一选择。如果你在嵌入式平台,内存紧张,那就换手写指针版。如果你处理的是超大矩阵,且性能瓶颈恰好卡在这个统计上,再考虑并行。

还有一个容易被忽略的点:如果统计操作发生在视频循环里,要避免在每一帧里反复分配 mask。虽然 OpenCV 的 Mat 在底层做了引用计数和内存复用,但频繁的分配释放依然会有波动。你可以提前声明一个 mask,在循环外用create保证尺寸类型,然后每帧只调 compare 和 countNonZero,降低分配频率。

我在某个图像后处理项目里,最初就是拿 at 版本统计深度图的负值无效区域,一帧 1080p 卡在几十毫秒。后来换成 compare + countNonZero,时间掉到 1 毫秒以内。当时最大的感触就是:很多所谓的“算法慢”,其实只是工具选错了。所以如果你只需要一个数,真的别再自己一层层写循环了。

相关新闻

从囤积资料到掌握技能:个人技能管理闭环方法论

从囤积资料到掌握技能:个人技能管理闭环方法论

你有没有过这种感觉&#xff1a;收藏夹里躺了几百个教程&#xff0c;网盘里存了一堆学习资料&#xff0c;每天刷各种干货文章&#xff0c;觉得自己一直在"学东西"&#xff0c;可真到了要用的时候&#xff0c;却发现自己什么都不会&#xff1f;我太熟悉这种状态了。前…

2026/10/21 4:32:49 阅读更多 →
OpenClaw实战:让大模型能动手的本地执行引擎

OpenClaw实战:让大模型能动手的本地执行引擎

1. 为什么需要OpenClaw&#xff1a;从聊天到执行的缺口1.1 聊天再聪明&#xff0c;也替你做不了事这两年我用过不少AI助手&#xff0c;从网页版到本地部署的各种开源模型都折腾过。说实话&#xff0c;光论对话能力&#xff0c;现在的开源模型已经相当能打了。写文案、列提纲、解…

2026/10/21 4:32:49 阅读更多 →
REA:面向高频数据场景的前端实时事件聚合器设计与实践

REA:面向高频数据场景的前端实时事件聚合器设计与实践

半年前我在本地新建了一个空目录&#xff0c;名字就叫rea&#xff0c;没有说明文档&#xff0c;没有 package.json&#xff0c;只有三个字母。后来我给它补全了一个含义&#xff1a;REA Real-time Event Aggregator&#xff0c;一个轻量级实时事件聚合器。这算是把项目名从“名…

2026/10/21 4:32:49 阅读更多 →

最新新闻

手写JSON解析器:从词法分析到语法树的编译原理实战

手写JSON解析器:从词法分析到语法树的编译原理实战

1. 从一段JSON文本到内存对象&#xff1a;解析器到底在做什么很多人第一次接触编译原理&#xff0c;都是从"写一个JSON解析器"开始的。这个选择很聪明——JSON的语法足够简单&#xff0c;简单到一页纸就能写完文法&#xff1b;但它又足够完整&#xff0c;完整到能覆盖…

2026/10/21 5:11:04 阅读更多 →
Java俄罗斯方块源码解析:游戏主循环、碰撞检测与消行实现

Java俄罗斯方块源码解析:游戏主循环、碰撞检测与消行实现

简介&#xff1a;这是一份面向Java初学者与游戏编程爱好者的俄罗斯方块完整源码&#xff0c;以Java语言实现&#xff0c;适合用来理解经典游戏的核心逻辑与面向对象设计。源码围绕游戏主面板、方块形状、流程控制器与程序入口等关键类展开&#xff0c;涵盖游戏循环、方块生成与…

2026/10/21 5:11:04 阅读更多 →
NLP与无人船:从自然语言指令到自主靠泊的完整技术链路

NLP与无人船:从自然语言指令到自主靠泊的完整技术链路

去年我带着组里的几位同学把一个挺有意思的项目从零做到了仿真验证——给一艘无人船加一套自然语言操控链路&#xff0c;让操作员可以直接说“慢速驶向测试场北侧码头&#xff0c;左舷靠泊”或者“绕开右前方漂浮物&#xff0c;停到A区泊位”&#xff0c;船载系统自己去完成意图…

2026/10/21 5:11:04 阅读更多 →
鸿蒙+Flutter跨平台实战:桌面图标焕新方案全解析

鸿蒙+Flutter跨平台实战:桌面图标焕新方案全解析

桌面图标焕新大师这个名字&#xff0c;听起来像某个手机美化App的宣传语&#xff0c;但它本质上是一个很典型的跨平台实战项目&#xff1a;把视觉设计、资源管理、系统桌面机制和Flutter渲染能力串在一起&#xff0c;最终在鸿蒙设备上完成“一键切换桌面图标风格”的完整闭环。…

2026/10/21 5:11:04 阅读更多 →
从ReAct到龙虾架构:AI Agent编排演进与工程实践

从ReAct到龙虾架构:AI Agent编排演进与工程实践

这两年AI Agent架构的文章我看了不下几十篇&#xff0c;名字一个比一个唬人&#xff1a;ReAct、Plan-and-Execute、Reflexion、多Agent系统、图式编排、龙虾架构……但真能把“从A到B是怎么一步步走出来”讲明白的&#xff0c;确实不多。尤其对小程序员来说&#xff0c;最痛苦的…

2026/10/21 5:11:04 阅读更多 →
AI原生知识库重构:从传统检索到语义网络的实践解析

AI原生知识库重构:从传统检索到语义网络的实践解析

做了三年传统知识库&#xff0c;我一直在琢磨一个问题&#xff1a;资料越存越多&#xff0c;知识却越来越难找&#xff0c;团队知道的东西永远比系统记录的多。传统知识库的把式——目录分类、关键词搜索、标签管理——本质上是在用二维平面的方式梳理立体复杂的真实知识&#…

2026/10/21 5:10:03 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →