OpenCV 特征匹配实战:用 FlannBasedMatcher 与 FLANN 模块实现快速高效的特征点匹配
本文以 OpenCV 官方教程文档 doc/tutorials/features/feature_flann_matcher/feature_flann_matcher.markdown 为核心骨架,结合仓库中 C++ / Java / Python 三种语言的完整示例代码,以及 features 与 flann 模块的真实源码实现,系统讲解基于 FLANN 的近邻搜索式特征匹配:从距离度量与 Lowe 比率测试的原理,到 FlannBasedMatcher 的创建、knnMatch 调用与结果筛选的完整流程。读完本文,你将掌握如何在 OpenCV 中直接用 FlannBasedMatcher 接口完成"特征检测 → 描述子计算 → 快速近邻匹配 → 比率测试过滤"的标准链路,并理解其在源码层面如何把匹配委托给 flann::Index 完成高效搜索。
适用说明:本教程对应的官方标注兼容性为 OpenCV >= 3.0;本文所有源码路径均以当前仓库目录结构为准。
教程目标
本教程源自官方 OpenCV 特征匹配系列,目标是学会:
- 使用
cv::FlannBasedMatcher(FLANN 描述子匹配器)接口; - 借助
flann模块对大规模描述子集合执行快速且高效的匹配。
关于描述子来源需要特别注意:由于示例默认使用 SURF 特征,而 SURF 位于 opencv_contrib 的 xfeatures2d 模块中,因此需要安装 contrib 模块才能运行(也可以替换为 ORB、KAZE 等其他特征)。这一点在文档中有明确警示,示例代码也通过 HAVE_OPENCV_XFEATURES2D 宏做了编译期保护——当 contrib 模块缺失时,程序会输出提示信息后退出(见 SURF_FLANN_matching_Demo.cpp)。
原理:为什么需要 FLANN 这类近邻搜索
特征描述子的匹配本质
特征匹配的第一步是计算两幅图像各自的关键点描述子;第二步则是为第一幅图像中的每个描述子,在第二幅图像的描述子集合中寻找"最相似"的一个。经典浮点型描述子(SIFT、SURF 等)通常用**欧氏距离(L2 范数)**衡量相似度。
SIFT/SURF 描述子在数学本质上是局部邻域内方向梯度直方图(对 SURF 而言是 Haar 小波响应的直方图),因此除了欧氏距离,也可以使用直方图类度量,例如 χ²(卡方)距离、推土机距离(Earth Mover's Distance,EMD)等。
文档还引用了 Arandjelovic 等人提出的 RootSIFT 扩展思想:用平方根(Hellinger)核替代标准欧氏距离来衡量 SIFT 描述子之间的相似度,能够在流水线的各个阶段带来显著的性能提升。这类研究结论提醒我们:距离度量的选择本身也是匹配质量优化的一环,而不仅是匹配算法本身。
二值描述子与汉明距离
与浮点描述子不同,二值描述子(ORB、BRISK 等)用 0/1 位串表示特征,匹配时使用的是汉明距离(Hamming distance)。其计算等价于:对两个位串做异或(XOR)操作后再统计置位个数(population count):
[ d_{hamming}(a,b) = \sum_{i=0}^{n-1} (a_i \oplus b_i) ]
从暴力匹配到 FLANN
OpenCV 中默认存在两类匹配策略:暴力匹配器(BFMatcher)逐对比较每一个查询描述子与训练描述子,在大规模集合上开销可观;而 FlannBasedMatcher 则先在训练描述子集合上构建 cv::flann::Index 索引,再通过其近邻搜索方法完成匹配。
这一点在 features 模块头文件对 FlannBasedMatcher 的类注释中写得很明确(见 features.hpp):该匹配器会在训练描述子集合上训练 flann::Index 并调用其最近邻搜索方法,因此当训练集合很大时,它可能比暴力匹配更快。不过也正因 flann::Index 本身不支持掩码(mask),FlannBasedMatcher 同样不支持对描述子集合做掩码约束——这正是它与 BFMatcher 的显著差异之一。
匹配结果过滤:Lowe 比率测试与其它策略
比率测试的理论依据
直接取"最近邻"往往混入大量误匹配。Lowe 在其 SIFT 论文中提出**距离比率测试(distance ratio test)**来剔除错误匹配:对每个关键点,取出距离最近的两个匹配(最近邻与次近邻),计算两者距离之比,当该比值低于某个阈值时才判定为正确匹配。
其直觉在于:能够很好区分的匹配,其最近邻距离会显著小于次近邻距离(比值远小于 1);而模糊、歧义的匹配,两个最近邻距离接近(比值趋近于 1),难以判断真伪。下图即 SIFT 论文中"基于最近邻距离比率测试的匹配正确概率"示意,实线为正确匹配的概率密度分布,虚线为错误匹配的分布:
从图中可直观看到:正确匹配的距离比集中在较小区间,错误匹配则向 1 靠拢,因此设定阈值(示例取 0.7)即可显著剔除误匹配。
其它可选的补充过滤
文档同时给出两类备选/补充的过滤思路:
- 交叉检验(cross check test):若特征 f_b 是 f_a 在图像 I_b 中的最佳匹配,且 f_a 也是 f_b 在 I_a 中的最佳匹配,才认定 (f_a, f_b) 为"好匹配"。在
features模块中,BFMatcher::create的crossCheck参数即可开启该行为(见 features.hpp),但其类注释同样说明,该技术与比率测试互为替代方案,通常能取得最少离群点的结果。 - 几何校验(geometric test):剔除不符合几何模型(如对平面物体使用 RANSAC 鲁棒单应估计)的匹配。这正是本教程的后续篇章 Feature Homography 教程 的主题。
示例代码与运行数据
教程提供了三种语言的完整示例,位于仓库以下路径:
示例默认输入图片为仓库 samples 数据目录下的 box.png(目标物体)与 box_in_scene.png(包含该物体的场景图),均位于 samples/data。三份代码统一通过命令行解析器接收图片路径参数,C++ 端支持 --input1、--input2、--help(见 SURF_FLANN_matching_Demo.cpp),并在读图失败时打印用法后退出,运行示例形如:
./SURF_FLANN_matching_Demo --input1=box.png --input2=box_in_scene.png
Python 端等价写法:
python SURF_FLANN_matching_Demo.py --input1=box.png --input2=box_in_scene.png
完整 C++ 代码
#include <iostream>
#include "opencv2/core.hpp"
#ifdef HAVE_OPENCV_XFEATURES2D
#include "opencv2/highgui.hpp"
#include "opencv2/features.hpp"
#include "opencv2/xfeatures2d.hpp"
using namespace cv;
using namespace cv::xfeatures2d;
using std::cout;
using std::endl;
const char* keys =
"{ help h | | Print help message. }"
"{ input1 | box.png | Path to input image 1. }"
"{ input2 | box_in_scene.png | Path to input image 2. }";
int main( int argc, char* argv[] )
{
CommandLineParser parser( argc, argv, keys );
Mat img1 = imread( samples::findFile( parser.get<String>("input1") ), IMREAD_GRAYSCALE );
Mat img2 = imread( samples::findFile( parser.get<String>("input2") ), IMREAD_GRAYSCALE );
if ( img1.empty() || img2.empty() )
{
cout << "Could not open or find the image!\n" << endl;
parser.printMessage();
return -1;
}
//-- Step 1: Detect the keypoints using SURF Detector, compute the descriptors
int minHessian = 400;
Ptr<SURF> detector = SURF::create( minHessian );
std::vector<KeyPoint> keypoints1, keypoints2;
Mat descriptors1, descriptors2;
detector->detectAndCompute( img1, noArray(), keypoints1, descriptors1 );
detector->detectAndCompute( img2, noArray(), keypoints2, descriptors2 );
//-- Step 2: Matching descriptor vectors with a FLANN based matcher
// Since SURF is a floating-point descriptor NORM_L2 is used
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create(DescriptorMatcher::FLANNBASED);
std::vector< std::vector<DMatch> > knn_matches;
matcher->knnMatch( descriptors1, descriptors2, knn_matches, 2 );
//-- Filter matches using the Lowe's ratio test
const float ratio_thresh = 0.7f;
std::vector<DMatch> good_matches;
for (size_t i = 0; i < knn_matches.size(); i++)
{
if (knn_matches[i][0].distance < ratio_thresh * knn_matches[i][1].distance)
{
good_matches.push_back(knn_matches[i][0]);
}
}
//-- Draw matches
Mat img_matches;
drawMatches( img1, keypoints1, img2, keypoints2, good_matches, img_matches, Scalar::all(-1),
Scalar::all(-1), std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
//-- Show detected matches
imshow("Good Matches", img_matches );
waitKey();
return 0;
}
#else
int main()
{
std::cout << "This tutorial code needs the xfeatures2d contrib module to be run." << std::endl;
return 0;
}
#endif
完整 Python 代码
from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
parser = argparse.ArgumentParser(description='Code for Feature Matching with FLANN tutorial.')
parser.add_argument('--input1', help='Path to input image 1.', default='box.png')
parser.add_argument('--input2', help='Path to input image 2.', default='box_in_scene.png')
args = parser.parse_args()
img1 = cv.imread(cv.samples.findFile(args.input1), cv.IMREAD_GRAYSCALE)
img2 = cv.imread(cv.samples.findFile(args.input2), cv.IMREAD_GRAYSCALE)
if img1 is None or img2 is None:
print('Could not open or find the images!')
exit(0)
#-- Step 1: Detect the keypoints using SURF Detector, compute the descriptors
minHessian = 400
detector = cv.xfeatures2d_SURF.create(hessianThreshold=minHessian)
keypoints1, descriptors1 = detector.detectAndCompute(img1, None)
keypoints2, descriptors2 = detector.detectAndCompute(img2, None)
#-- Step 2: Matching descriptor vectors with a FLANN based matcher
# Since SURF is a floating-point descriptor NORM_L2 is used
matcher = cv.DescriptorMatcher_create(cv.DescriptorMatcher_FLANNBASED)
knn_matches = matcher.knnMatch(descriptors1, descriptors2, 2)
#-- Filter matches using the Lowe's ratio test
ratio_thresh = 0.7
good_matches = []
for m,n in knn_matches:
if m.distance < ratio_thresh * n.distance:
good_matches.append(m)
#-- Draw matches
img_matches = np.empty((max(img1.shape[0], img2.shape[0]), img1.shape[1]+img2.shape[1], 3), dtype=np.uint8)
cv.drawMatches(img1, keypoints1, img2, keypoints2, good_matches, img_matches, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
#-- Show detected matches
cv.imshow('Good Matches', img_matches)
cv.waitKey()
完整 Java 代码
import java.util.ArrayList;
import java.util.List;
import org.opencv.core.Core;
import org.opencv.core.DMatch;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.core.Scalar;
import org.opencv.features.DescriptorMatcher;
import org.opencv.features.Features;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.xfeatures2d.SURF;
class SURFFLANNMatching {
public void run(String[] args) {
String filename1 = args.length > 1 ? args[0] : "../data/box.png";
String filename2 = args.length > 1 ? args[1] : "../data/box_in_scene.png";
Mat img1 = Imgcodecs.imread(filename1, Imgcodecs.IMREAD_GRAYSCALE);
Mat img2 = Imgcodecs.imread(filename2, Imgcodecs.IMREAD_GRAYSCALE);
if (img1.empty() || img2.empty()) {
System.err.println("Cannot read images!");
System.exit(0);
}
//-- Step 1: Detect the keypoints using SURF Detector, compute the descriptors
double hessianThreshold = 400;
int nOctaves = 4, nOctaveLayers = 3;
boolean extended = false, upright = false;
SURF detector = SURF.create(hessianThreshold, nOctaves, nOctaveLayers, extended, upright);
MatOfKeyPoint keypoints1 = new MatOfKeyPoint(), keypoints2 = new MatOfKeyPoint();
Mat descriptors1 = new Mat(), descriptors2 = new Mat();
detector.detectAndCompute(img1, new Mat(), keypoints1, descriptors1);
detector.detectAndCompute(img2, new Mat(), keypoints2, descriptors2);
//-- Step 2: Matching descriptor vectors with a FLANN based matcher
// Since SURF is a floating-point descriptor NORM_L2 is used
DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.FLANNBASED);
List<MatOfDMatch> knnMatches = new ArrayList<>();
matcher.knnMatch(descriptors1, descriptors2, knnMatches, 2);
//-- Filter matches using the Lowe's ratio test
float ratioThresh = 0.7f;
List<DMatch> listOfGoodMatches = new ArrayList<>();
for (int i = 0; i < knnMatches.size(); i++) {
if (knnMatches.get(i).rows() > 1) {
DMatch[] matches = knnMatches.get(i).toArray();
if (matches[0].distance < ratioThresh * matches[1].distance) {
listOfGoodMatches.add(matches[0]);
}
}
}
MatOfDMatch goodMatches = new MatOfDMatch();
goodMatches.fromList(listOfGoodMatches);
//-- Draw matches
Mat imgMatches = new Mat();
Features.drawMatches(img1, keypoints1, img2, keypoints2, goodMatches, imgMatches, Scalar.all(-1),
Scalar.all(-1), new MatOfByte(), Features.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS);
//-- Show detected matches
HighGui.imshow("Good Matches", imgMatches);
HighGui.waitKey(0);
System.exit(0);
}
}
public class SURFFLANNMatchingDemo {
public static void main(String[] args) {
// Load the native OpenCV library
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
new SURFFLANNMatching().run(args);
}
}
关键步骤逐段解析
整个流程高度结构化,分四个阶段,三份代码逻辑完全一致,仅语言 API 表达略有差异。
Step 1:SURF 关键点检测与描述子计算
int minHessian = 400;
Ptr<SURF> detector = SURF::create( minHessian );
detector->detectAndCompute( img1, noArray(), keypoints1, descriptors1 );
detector->detectAndCompute( img2, noArray(), keypoints2, descriptors2 );
minHessian = 400是 SURF 检测器的 Hessian 阈值:数值越大,检测出的关键点越"显著"、数量越少,反之则越多。detectAndCompute一步同时完成关键点检测与描述子计算,这里以灰度图IMREAD_GRAYSCALE读取输入。- 语言差异点:C++/Python 只需传 Hessian 阈值,而 Java 版
SURF.create(hessianThreshold, nOctaves, nOctaveLayers, extended, upright)还显式列出了八度数量(4)、层数(3)、是否扩展(false)与是否直立(false),与默认构造语义一致。 - 若未安装 contrib 的 xfeatures2d,SURF 不可用——此时可退而使用 ORB、KAZE 等
features模块内置特征(这也是文档给出的替代方案)。
Step 2:用 FLANN 匹配器做 k 近邻匹配
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create(DescriptorMatcher::FLANNBASED);
std::vector< std::vector<DMatch> > knn_matches;
matcher->knnMatch( descriptors1, descriptors2, knn_matches, 2 );
这里有两个值得展开的要点:
其一,匹配器通过工厂方法统一创建。 在 features 模块的 matchers.cpp 中,DescriptorMatcher::create(const String&) 按名称分发:"FlannBased" 对应 makePtr<FlannBasedMatcher>()(需要 HAVE_OPENCV_FLANN 宏开启 flann 模块),"BruteForce" 等则分别对应不同范数的 BFMatcher。而枚举版工厂 DescriptorMatcher::create(DescriptorMatcher::FLANNBASED) 只是先把枚举映射成字符串再走同一套逻辑(见 matchers.cpp)。MatcherType 枚举定义在 features.hpp:FLANNBASED = 1、BRUTEFORCE = 2、BRUTEFORCE_L1 = 3、BRUTEFORCE_HAMMING = 4、BRUTEFORCE_HAMMINGLUT = 5、BRUTEFORCE_SL2 = 6。
其二,knnMatch(..., 2) 表示对每个查询描述子返回 2 个最近邻(knn = 2),这一步正是为 Step 3 的 Lowe 比率测试做数据准备——因为比率需要"最近邻/次近邻"。输出 knn_matches 的每一行 knn_matches[i] 是一个含 k 个 DMatch 的数组(按距离升序排列),DMatch 携带 queryIdx、trainIdx、imgIdx 与 distance 字段。方法本身在 features.hpp 中声明。
Step 3:Lowe 比率测试过滤
const float ratio_thresh = 0.7f;
std::vector<DMatch> good_matches;
for (size_t i = 0; i < knn_matches.size(); i++)
{
if (knn_matches[i][0].distance < ratio_thresh * knn_matches[i][1].distance)
{
good_matches.push_back(knn_matches[i][0]);
}
}
- 判定条件是
最近邻距离 < 0.7 × 次近邻距离,满足则把最近邻判定为好匹配。 - 阈值越小,过滤越严格、保留的匹配越少但越可靠;0.7 是广泛使用的经验值。Java 版本额外判断了
rows() > 1,确保该查询点确实有 2 个近邻可供比较后再取matches[0]、matches[1],避免下标越界——这是对同一逻辑更稳健的防御性写法。
Step 4:绘制并显示匹配
drawMatches( img1, keypoints1, img2, keypoints2, good_matches, img_matches, Scalar::all(-1),
Scalar::all(-1), std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
imshow("Good Matches", img_matches );
waitKey();
drawMatches 把两幅图并排绘制并把过滤后的好匹配以彩色连线标出;DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS 表示不绘制没有匹配线的孤立关键点。Scalar::all(-1) 表示连线与关键点颜色由 OpenCV 随机指定,便于区分不同匹配。Java 端对等 API 是 Features.drawMatches,Python 端是 cv.drawMatches(注意 cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS 的下划线命名风格)。
源码级剖析:FlannBasedMatcher 究竟做了什么
深入 features 模块源码可以看到 FLANN 匹配器内部真正的执行链路。
索引与搜索参数的默认值
FlannBasedMatcher 的构造函数声明(见 features.hpp):
FlannBasedMatcher( const Ptr<flann::IndexParams>& indexParams = makePtr<flann::KDTreeIndexParams>(),
const Ptr<flann::SearchParams>& searchParams = makePtr<flann::SearchParams>() );
即默认使用 KD-Tree 索引(KDTreeIndexParams,默认 trees=4)与 SearchParams(默认 checks=32,即搜索时最多检查 32 个叶节点,数值越大越精确、耗时越高)。相关参数结构体定义在 miniflann.hpp,同文件还提供 LinearIndexParams、CompositeIndexParams、AutotunedIndexParams、HierarchicalClusteringIndexParams、KMeansIndexParams、LshIndexParams(面向二值描述子的局部敏感哈希)以及用于持久化索引的 SavedIndexParams。flann::Index 构造时默认采用 FLANN_DIST_L2 距离(miniflann.hpp),与 SURF/SIFT 浮点描述子匹配的场景相匹配。
train():构建索引
匹配前需要先训练。FlannBasedMatcher::train()(matchers.cpp)会把累加的 trainDescCollection 合并为 mergedDescriptors,再据此构建 flann::Index。也就是说:索引一旦建立,后续每个查询描述子都通过该索引快速检索近邻,这正是相对 BFMatcher 提速的根本。
knnMatchImpl():近邻检索与距离转换
FlannBasedMatcher::knnMatchImpl()(matchers.cpp)的实现在底层调用 flannIndex->knnSearch(queryDescriptors, indices, dists, knn, *searchParams),把搜索委托给 flann::Index::knnSearch(声明见 miniflann.hpp)。
随后 convertToDMatches()(matchers.cpp)负责把搜索结果转换为标准 DMatch 结构,其中有两点实现细节值得注意:
- 无效近邻通过负索引跳过(
idx >= 0才输出); - 距离类型为
CV_32F(浮点、L2 情形)时会执行sqrt(dists.at<float>(i, j))得到真正的欧氏距离,而CV_32S(整型,如 Hamming 距离情形)则原样取整数值。这解释了为什么knnMatch返回的DMatch::distance与一些直接暴露 squared-L2 的底层 FLANN 返回值存在开方差异。
掩码支持与局限
FlannBasedMatcher 的实现(isMaskSupported() 返回不支持)以及头文件类注释都确认:FLANN 匹配器不支持掩码约束匹配,根本原因在于底层 flann::Index 没有掩码能力。因此,若需要按 ROI 或连通性掩码过滤候选,应改用支持掩码的 BFMatcher。
调参建议、边界与注意事项
把以上原理落到工程实践,可总结出以下实操要点:
- 阈值语义:
ratio_thresh(本示例0.7)控制"最近邻相对于次近邻必须显著更近"的强度。追求高准确率可下调到0.6左右;场景模糊、关键点稀疏时可适度上调以保留更多匹配,再辅以几何校验。 - 描述子类型决定距离与索引:SURF/SIFT 浮点描述子走默认 KD-Tree + L2 即可;ORB/BRISK 等二值描述子按文档理论应使用汉明距离,对应 FLANN 场景通常需要切换为
LshIndexParams类索引,并注意flann::Index构造时默认距离是 L2,需按描述子类型显式配置。 - 数据规模决定匹配器选型:
FlannBasedMatcher的类注释明确其优势在于"大训练集合"。小数据集上建树开销可能超过暴力匹配的收益,直接使用BFMatcher(L2)反而更简单;同时 FLANN 不持掩码,需要掩码时应回退 BFMatcher。 - Java 边界检查:Java 示例对每一行先判断
rows() > 1再取两个近邻,这在某些关键点数量不足导致近邻不足时能防止越界,值得在 C++/Python 版本中同样保留防御。 - 贡献模块依赖:SURF 位于 contrib 的 xfeatures2d;未安装时程序会提示需该模块(或换用 ORB/KAZE)。
运行结果
下图是示例在 box.png 与 box_in_scene.png 上执行 SURF 特征匹配、并经 Lowe 比率测试(阈值 0.7)过滤后绘制的匹配结果。左图为待匹配的目标物体,右图为包含该物体的场景,彩色连线将两幅图中彼此对应的特征点相连,过滤后保留的连线基本都对应真实对应点,错误匹配被有效抑制:
仓库中与本文内容配套的自动化测试也验证了同一套用法:例如 test_flann_based_matcher.py 通过 cv2.FlannBasedMatcher(index_param, search_param) 构建匹配器并调用 knnMatch(..., k=2);test_feature_homography.py 展示了"ORB 特征 + FLANN(LSH 索引)+ knnMatch"在实际跟踪管线中的应用,可作为二值描述子使用 FLANN 的参考实现。
延伸阅读
本教程是 OpenCV 特征系列的一环:上游是特征检测与描述子计算教程 feature_description,下游则在得到过滤后的匹配之后,进一步用 findHomography 与 perspectiveTransform 估计两图间的变换以定位已知物体,即 Feature Homography 教程,并最终扩展到 平面物体检测教程。如果你想在一个大图库/视频序列中检索目标,或为 SLAM、图像拼接(stitching)提供特征对应,掌握 FlannBasedMatcher + 比率测试 + 几何校验这条管线,就是最实用的起点。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

