唯杰地图 logo唯杰地图
VJMAP
EN

表格识别(extractTables)

vjcad/references/recognition/table-extract.md 6.9 KB156 行
原文

文档: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