表格识别(extractTables)
文档:
vjcad/doc/recognition/table-extract.md示例:vjcad/example/recognition/01extracttable.js
1. 端到端最小例子
下例先用代码画一张 3×3 的小表,再调 extractTables:
class TableDrawingExtraction {
async main() {
await this.draw3x3Table(0, 0);
await vjcad.delay(500); // ⚠️ delay 是顶层导出函数,不是 Engine.delay
const result = extractTables({ debug: true });
if (result.error) return message.error(result.error);
if (!result.tables.length) return message.warn("未识别到表格");
result.tables.forEach((t, i) => {
const lines = [
`表格 ${i + 1}:${t.rowCount} 行 × ${t.colCount} 列`,
...t.datas.map(row =>
"| " + row.map(c => (c || "").padEnd(8, " ")).join(" | ") + " |"
)
];
console.log(lines.join("\n"));
});
}
async draw3x3Table(ox, oy) {
const cw = 50, rh = 25;
// 画 4 条横线 + 4 条竖线
for (let i = 0; i <= 3; i++) {
const ln = new LineEnt([ox, oy + i * rh], [ox + 3 * cw, oy + i * rh]);
ln.setDefaults(); Engine.addEntities(ln);
}
for (let j = 0; j <= 3; j++) {
const ln = new LineEnt([ox + j * cw, oy], [ox + j * cw, oy + 3 * rh]);
ln.setDefaults(); Engine.addEntities(ln);
}
// 写文字(坐标取单元中心)
const data = [["编号", "名称", "数量"], ["1", "螺栓", "10"], ["2", "螺母", "20"]];
for (let r = 0; r < 3; r++) for (let c = 0; c < 3; c++) {
// TextEnt ctor: (insertionPoint, textString, height, ...)
const t = new TextEnt([ox + c * cw + cw / 2, oy + (2 - r) * rh + rh / 2], data[r][c], 8);
t.setDefaults(); Engine.addEntities(t);
}
}
}
完整代码:
vjcad/example/recognition/01extracttable.js
2. TableData 结构详解
interface TableData {
rect: string; // 两对角点逗号串 "x1,y1,x2,y2"(WCS,无方括号,不保证 min/max 顺序)
rowCount: number;
colCount: number;
rows: string[]; // 行分隔线 y 坐标,数字字符串数组(自上而下)
cols: string[]; // 列分隔线 x 坐标,数字字符串数组
spans: Record<string, string>; // 合并单元格(已是对象)"row_col"(1 起算)-> "rowSpan,colSpan"
datas: string[][]; // 二维数组 [row][col],元素是单元格**文本字符串**
attr: TableAttr; // 识别统计信息
}
interface TableAttr {
cellEmptyRatio: number; // 空单元百分比(0–100)
tableTextCount: number; // 表内文字总数
tableCellMaxCount: number; // 最大单元数
unLinkLineRatio: number; // 未关联线比例
}
datas[r][c]按 行优先 / 顶部为 row=0 排列,元素是字符串(空单元为""),不是带.text/.rect的对象。
3. 范围与图层限定
// 拿到当前可识别图层名
const layers = getTableExtractLayers();
console.log(layers);
// 只识别"表格"图层 + 指定矩形
extractTables({
bounds: { minX: 0, minY: 0, maxX: 1000, maxY: 800 },
layers: ['表格'],
});
bounds 是 { minX, minY, maxX, maxY } 对象(Bounds),不是数组。让用户拉框用 Engine.getPoint + Engine.getCorner(顶层 getCorner 也可):
const p1 = await Engine.getPoint(new PointInputOptions('指定第一角点:'));
if (p1.status !== InputStatusEnum.OK) return;
const p2 = await Engine.getCorner(new CornerInputOptions('指定对角点:', p1.value));
if (p2.status !== InputStatusEnum.OK) return;
extractTables({
bounds: {
minX: Math.min(p1.value.x, p2.value.x),
minY: Math.min(p1.value.y, p2.value.y),
maxX: Math.max(p1.value.x, p2.value.x),
maxY: Math.max(p1.value.y, p2.value.y),
},
});
4. 调优建议
| 症状 | 解决 |
|---|---|
| 找不到表格 | tol 调大;或 includeBlocks: true 展开图块 |
| 单元被错切 | tableEdgeMinPoint 调大,过滤短线段 |
| 表格被分裂成多个 | 用 bounds 限定区域;或检查闭合误差 |
| 文字找不到 | 看是否 MText —— 默认 includeMText: true,但被关掉时要打开 |
| 中文乱码 | 缺字体;先确认 MainView({ fonts }) 已加载对应字体 |
| 算得太慢 | 加 bounds + layers,避免全图扫描 |
debug: true 会输出每一步候选数量,是调优最有效的开关。
5. 完整选项类型
interface TableExtractOptions {
bounds?: Bounds; // { minX, minY, maxX, maxY },不传 = 全图
layers?: string[]; // 限定参与图层(大小写不敏感)
includeLine?: boolean; // 计入 LINE(默认计)
includePolyline?: boolean; // 计入 PLINE/POLYLINE(默认计)
includeText?: boolean; // 计入 TEXT(默认计)
includeMText?: boolean; // 计入 MTEXT(默认计)
includeBlocks?: boolean; // 展开 INSERT 内实体(默认展开)
includeGroups?: boolean; // 展开 GROUP 内实体(默认展开)
digit?: number; // 几何比较精度(默认 4)
tol?: number; // 端点对齐容差,默认 0 = 引擎自适应
tableEdgeMinPoint?: number; // 表格边框最少顶点数(默认 12)
tableTextMinCount?: number; // 表内最少文字数(默认 4)
cellEmptyRatio?: number; // 允许的最大空单元百分比 0–100(默认 90),超过则整表丢弃
debug?: boolean; // 控制台输出中间过程
}
红旗
- 返回前请检查
result.error;如果有错,result.tables为空 - 大图(>10万实体)必须传
bounds + layers,否则容易卡顿 - "表格里有图片 / 印章" 时,禁用
includeBlocks避免把图章内文字也识别进去 cellEmptyRatio是百分比(默认 90):空单元占比超过它时整张表被丢弃(不是丢空单元);稀疏表识别不出时调大,切勿填0.5这类 0~1 小数(几乎所有表都会被丢弃)- 后续步骤想反向写回(修改单元文字):
datas[r][c]只有文本,要定位原文字实体需结合rect(split(',').map(Number))/rows/cols(.map(Number))的坐标用getEntities按范围查找——这些字段不是 JSON 串,不能JSON.parse;按范围查找时加e.isAlive过滤 extractTables与getEntities都包含已 erase 的实体(isAlive=false),识别结果可能含已删内容
参考(官方文档 MCP)
- 文档:
vjcad/doc/recognition/table-extract.md - 示例:
vjcad/example/recognition/01extracttable.js