最佳实践:自动驾驶图像高性能分析系统

在自动驾驶系统中,车辆图片分析是环境感知模块的核心场景之一,主要用于实时解析车辆内外部摄像头捕捉的视觉信息,以实现对周围环境的精准理解与决策。本文以BDD自动驾驶数据集为例模拟自动驾驶场景,模拟真实驾驶场景,采集10万张包括驾驶区域、地理、环境、天气多样性等数据的图片,通过对图片的分析与检索,进行了行驶轨迹分析、环境感知优化及行人车辆识别精度提升等全栈技术验证,提升系统对复杂交通场景的适应能力、安全性和用户体验。

核心能力介绍

本文主要实现对图片的加工与多模检索分析,包含的主要能力如下:

  • 非结构化数据(Object Table):支持通过表的形式读取OSS中非结构化数据(PDF、IMAGE、PPT等)。

  • AI Function:在Hologres中可以用标准SQL的方式调用Function,自动调用内置大模型,完成AI服务建设场景。

    • 数据加工:提供Embed、Chunk算子,可以对非结构化数据加工成结构化数据存储,无需使用外部算法就能自动Embed。

    • 数据检索和分析:提供ai_genai_summarize等算子,使用SQL就能对数据进行推理、问题总结以及翻译等能力。

  • Dynamic Table介绍:支持增量刷新模式对非结构化数据自动加工,每次只计算增量的数据有效减少重复计算,降低资源利用率。

  • 向量检索:支持标准SQL的向量检索,用于非结构化数据的相似度搜索、场景识别等,在同一个查询中可以自由地实现向量和标量的检索。

  • 全文检索:通过倒排索引、分词等机制实现对非结构化数据的高效检索,支持关键词匹配、短语检索等丰富的检索方式,实现更加灵活的检索。

方案优势

通过如上核心能力,在Hologres中对图片检索的核心优势如下:

  • 完整的AI数据处理流程:涵盖从数据Embed、Chunk、增量加工和检索/分析的全流程,与使用大数据系统一样轻松构建AI应用。

  • 标准SQL加工图片数据:无需使用专用开发语言,SQL就能完成图片数据提取、加工。

  • 一个平台支持跨模态检索:支持以文搜图、以图搜图,语义理解突破关键词局限,在Hologres就能实现跨模态检索。

  • 检索更精准、灵活和智能:可以轻松构建“关键词+语义+多模态”的混合检索链路,覆盖从精准搜索到意图理解的全场景需求。还能结合AI Function实现对用户意图的深度理解,语义关联和上下文推理,实现更智能的检索能力。

  • 数据不出库,安全性更高:不需要将数据导出到外部系统,与Hologres的多种安全能力无缝集成,并高效保护数据安全。

方案流程介绍

本次方案的流程如下:

  1. 数据集准备。

    将图片数据上传至OSS存储。

  2. 图片加工。

    使用Object Table读取图片的元数据信息,然后创建增量刷新的Dynamic Table对数据进行Embed,并为Dynamic Table构建向量索引,以便后续检索能够充分利用索引的功能。

  3. 使用ai_embed算子将自然语言的问题进行Embedding,然后使用向量检索输出Top N的结果。

image

准备工作

  • 数据准备

    本文使用ModelScope公开的BDD100K 自动驾驶图像数据集val.zip文件,模拟多个车辆真是行驶数据。

  • 环境准备

    1. 购买Hologres V4.0及以上版本实例并创建数据库

    2. 购买AI资源

      本文以large-96core-512GB-384GB、1个节点为例。

    3. 模型部署。本次方案使用的模型以及分配的资源为:

      类别

      示例值

      模型名称

      image_embed

      模型类别

      clip-ViT-B-32

      模型作用描述

      图片Embedding

      单副本CPU(Core)

      7

      单副本内存

      30 GB

      单副本GPU

      1卡(96 GB)

      资源副本数

      1

      说明

      上述模型的资源均为默认分配的资源。

操作步骤

  1. 下载图片数据并导入至OSS。

    1. 下载BDD100K 自动驾驶图像数据集中的val.zip文件。

    2. 登录OSS管理控制台创建Bucket并将已下载的val.zip文件上传至该Bucket路径下。上传操作详情,请参见简单上传

      说明

      文件夹名称请使用小写。

  2. 账号授权。

    1. 登录RAM控制台,创建阿里云RAM角色并授予OSS的相关权限。

      推荐授予AliyunOSSReadOnlyAccess权限。

    2. 为上述阿里云RAM角色添加登录和Hologres的访问权限。

      • 阿里云账号(主账号)

        修改RAM角色的信任策略。重点需更新如下参数:

        • Action:更新为sts:AssumeRole

        • Service:更新为hologres.aliyuncs.com

        {
          "Statement": [
            {
              "Action": "sts:AssumeRole",
              "Effect": "Allow",
              "Principal": {
                "RAM": [
                  "acs:ram::1866xxxx:root"
                ],
                "Service": [
                  "hologres.aliyuncs.com"
                ]
              }
            }
          ],
          "Version": "1"
        }
      • RAM用户(子账号)

        1. RAM用户授权。

          1. 权限管理 > 权限策略页面,单击创建权限策略,并选择脚本编辑模式创建权限策略。具体操作,请参见创建自定义权限策略

            Hologres可通过该策略判断当前RAM用户是否具备创建对应RAM角色的权限。权限策略内容如下。

            {
              "Version": "1",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": "hologram:GrantAssumeRole",
                  "Resource": "<arn账号>"
                }
              ]
            }
          2. 身份管理 > 用户页面,单击目标RAM用户操作列中的添加权限,为RAM用户(子账号)授予上述步骤已创建的权限策略。具体操作,请参见RAM用户授权

        2. 为已创建的RAM角色授权。

          修改RAM角色的信任策略。重点需更新如下参数:

          • Action:更新为sts:AssumeRole

          • Service:更新为hologres.aliyuncs.com

          {
            "Statement": [
              {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                  "RAM": [
                    "acs:ram::1866xxxx:root"
                  ],
                  "Service": [
                    "hologres.aliyuncs.com"
                  ]
                }
              }
            ],
            "Version": "1"
          }
  3. 对图片进行Embedding。

    创建Object TableDynamic Table读取图片元数据,并对图片加工Embedding。因为流程较长,Hologres直接将过程封装成附录:存储过程。该存储过程包括的能力如下:

    • 创建一张Object Table,用于读取图片的元数据。

    • 创建一张增量刷新的Dynamic Table结果表,用于存储加工后的数据,并设置向量索引。该Dynamic Table未设置自动刷新,需要手动刷新。

    • Dynamic Table的刷新过程中会使用ai_embed对图片进行Embedding。

    该存储过程的使用如下:

    --存储过程,创建Object TableDynamic Table,使用dt对图片进行Embedding
    CALL create_image_table_from_oss(
        oss_path => 'oss://xxxx/bdd100k/val/images',
        oss_endpoint => 'oss-cn-hangzhou-internal.aliyuncs.com',
        oss_role_arn => 'acs:ram::1xxxx:role/xxxx',
        image_table => 'public.dt_image_bdd100k',
        embedding_model =>'image_embed'
    );
  4. 刷新结果表。

    通过上述步骤创建的Object TableDynamic Table都需要手动刷新,才能完成数据加工。该步骤已被封装为附录:存储过程,该存储过程包括的能力如下:

    • 刷新一次Object Table获取图片元数据。

    • 刷新一次Dynamic Table,进行图片的Embedding加工。

    该存储过程的使用如下:

    --刷新Dynamic Table,将图片Embedding
    CALL refresh_image_table(
        image_table => 'public.dt_image_bdd100k'
    );
    
  5. 图片检索。

    图片数据处理过后可以使用向量检索和AI Function进行检索。

    以文搜图

    如果使用clip-ViT-B-32模型进行以文搜图,问题请使用英文。如果是中文问题,请换成LLM模型。以文搜图的示例SQL如下:

    --以文搜图
    SELECT
        object_uri,
        approx_cosine_distance (embedding_vector, ai_embed ('image_embed', 'a red car in the rain')) AS score
    FROM
        public.dt_image_bdd100k
    ORDER BY
        score DESC
    LIMIT 1;
    
                                object_uri                         |  score   
    ---------------------------------------------------------------+-------
     oss://****/bd****k/val/images/b836b14a-fb13****.jpg| 0.322337151
    (5 rows)

    OSS中找到第一个结果的图片如下:

    image

    以图搜图

    以图搜图的示例SQL如下:

    --以图搜图
    SELECT
        object_uri,
        approx_cosine_distance (embedding_vector, ai_embed ('image_embed', to_file ('oss://xxxx/val/images/b9b53753-91a5d5f8.jpg', 'oss-cn-hangzhou-internal.aliyuncs.com', 'acs:ram::18xxx:role/xxx'))) AS score
    FROM
        public.dt_image_bdd100k
    WHERE
        object_uri <> 'oss://hm-**-hangzhou/bd****k/val/images/b9b53753-91a5****.jpg' --排除自身
    ORDER BY
        score DESC
    LIMIT 1;
    
                              object_uri                           |  score   
    ---------------------------------------------------------------+------
     oss://****/bd****k/val/images/c0e9b7c4-cd8b****.jpg | 0.918008327
    

    OSS中找到召回的图片,并做对比,结果如下:

    image

附录:存储过程

  • 创建Object TableDynamic Table

    -- Query查询结果默认限制200行,如需更多数据请修改limit,最多展示10000行或20M。
    CREATE OR REPLACE PROCEDURE create_image_table_from_oss(
        oss_path TEXT,
        oss_endpoint TEXT,
        oss_role_arn TEXT,
        image_table TEXT,
        embedding_model TEXT DEFAULT NULL,
        overwrite BOOLEAN DEFAULT FALSE
    )
    AS $$
    DECLARE
        image_schema_name TEXT;
        image_table_name TEXT;
        obj_table_name TEXT;
        full_image_table_ident TEXT;
        full_obj_ident TEXT;
        embed_expr TEXT;
        create_sql TEXT;
        embedding_dims INT;
    BEGIN
        -- 1. 拆 schema name + table name
        IF position('.' in image_table) > 0 THEN
            image_schema_name := split_part(image_table, '.', 1);
            image_table_name  := split_part(image_table, '.', 2);
        ELSE
            image_schema_name := 'public';
            image_table_name  := image_table;
        END IF;
    
        obj_table_name := image_table_name || '_obj_table';
    
        full_image_table_ident := format('%I.%I', image_schema_name, image_table_name);
        full_obj_ident    := format('%I.%I', image_schema_name, obj_table_name);
        
        -- 2. 如果需要覆盖,先删表和索引
        IF overwrite THEN
            DECLARE
                dyn_table_exists BOOLEAN;
                rec RECORD;
            BEGIN
                -- 检查 dynamic table 是否存在
                SELECT EXISTS (
                    SELECT 1
                    FROM pg_class c
                    JOIN pg_namespace n ON n.oid = c.relnamespace
                    WHERE c.relname = image_table_name
                    AND n.nspname = image_schema_name
                )
                INTO dyn_table_exists;
    
                IF dyn_table_exists THEN
                    -- 2.1 关闭动态表自动刷新
                    -- RAISE NOTICE 'Disabling auto refresh for %', full_image_table_ident;
                    -- EXECUTE format('ALTER TABLE IF EXISTS %s SET (auto_refresh_enable=false)', full_image_table_ident);
    
                    -- 2.2 查找 RUNNING 刷新任务并取消
                    FOR rec IN
                        EXECUTE format(
                            $f$
                            SELECT query_job_id
                                FROM hologres.hg_dynamic_table_refresh_log(%L)
                                WHERE status = 'RUNNING';
                            $f$,
                            image_table
                        )
                    LOOP
                        RAISE NOTICE 'Found running refresh job: %', rec.query_job_id;
                        IF hologres.hg_internal_cancel_query_job(rec.query_job_id::bigint) THEN
                            RAISE NOTICE 'Cancel job % succeeded.', rec.query_job_id;
                        ELSE
                            RAISE WARNING 'Cancel job % failed.', rec.query_job_id;
                        END IF;
                    END LOOP;
    
                    -- 2.3 删除 Dynamic Table
                    EXECUTE format('DROP TABLE IF EXISTS %s;', full_image_table_ident);
                ELSE
                    RAISE NOTICE 'Dynamic table % does not exist, skip cancel job and drop.', full_image_table_ident;
                END IF;
    
                -- 2.4 无论如何,Object Table 都要删除
                EXECUTE format('DROP OBJECT TABLE IF EXISTS %s;', full_obj_ident);
            END;
        END IF;
    
        -- 3. 创建 Object Table
        RAISE NOTICE 'Create object table: %', obj_table_name;
        EXECUTE format(
            $f$
            CREATE OBJECT TABLE %s
            WITH (
                path = %L,
                oss_endpoint = %L,
                role_arn = %L
            );
            $f$,
            full_obj_ident,
            oss_path,
            oss_endpoint,
            oss_role_arn
        );
    
        COMMIT;
    
        -- 4. 刷新 Object Table
        RAISE NOTICE 'Refresh object table: %', obj_table_name;
        EXECUTE format('REFRESH OBJECT TABLE %s;', full_obj_ident);
    
        COMMIT;
    
        -- 5. embedding 模型选择
        IF embedding_model IS NULL OR length(trim(embedding_model)) = 0 THEN
            embed_expr := 'ai_embed(file)';
    
            EXECUTE 'SELECT array_length(ai_embed(''dummy''), 1)'
            INTO embedding_dims;
        ELSE
            embed_expr := format('ai_embed(%L, file)', embedding_model);
    
            EXECUTE format(
                'SELECT array_length(ai_embed(%L, ''dummy''), 1)',
                embedding_model
            )
            INTO embedding_dims;
        END IF;
    
        RAISE NOTICE 'embedding dimension is: %', embedding_dims;
    
        -- 6. 创建 RAG 输出动态表
        RAISE NOTICE 'create dynamic table: %', image_table_name;
        EXECUTE format(
            $f$
            CREATE DYNAMIC TABLE %s(
                CHECK(array_ndims(embedding_vector) = 1 AND array_length(embedding_vector, 1) = %s)
            )
            WITH (
                vectors = '{
                    "embedding_vector": {
                        "algorithm": "HGraph",
                        "distance_method": "Cosine",
                        "builder_params": {
                        "base_quantization_type": "sq8_uniform",
                        "max_degree": 64,
                        "ef_construction": 400,
                        "precise_quantization_type": "fp32",
                        "use_reorder": true
                        }
                    }
                }',
                auto_refresh_mode = 'incremental',
                freshness = '5 minutes',
                auto_refresh_enable = 'false'
            ) AS
            SELECT
                object_uri,
                etag,
                %s AS embedding_vector
            FROM %s;
            $f$,
            full_image_table_ident,
            embedding_dims,
            embed_expr,
            obj_table_name
        );
    
        COMMIT;
    
        RAISE NOTICE '';
        RAISE NOTICE 'Create image table success: %', image_table;
        RAISE NOTICE '    Vector index is: %.embedding_vector', image_table;
    END;
    $$ LANGUAGE plpgsql;
    
    
    
    
  • Object TableDynamic Table

    CREATE OR REPLACE PROCEDURE refresh_image_table(
        image_table TEXT
    )
    AS $$
    DECLARE
        image_schema_name TEXT;
        image_table_name   TEXT;
        obj_table_name TEXT;
        full_image_table_ident TEXT;
        full_obj_ident    TEXT;
    BEGIN
        -- 1. 解析 schema 和表名
        IF position('.' in image_table) > 0 THEN
            image_schema_name := split_part(image_table, '.', 1);
            image_table_name  := split_part(image_table, '.', 2);
        ELSE
            image_schema_name := 'public';
            image_table_name  := image_table;
        END IF;
    
        obj_table_name := image_table_name || '_obj_table';
    
        full_image_table_ident := format('%I.%I', image_schema_name, image_table_name);
        full_obj_ident    := format('%I.%I', image_schema_name, obj_table_name);
    
        -- 2. 刷新 Object Table
        RAISE NOTICE 'Refreshing Object Table: %', obj_table_name;
        EXECUTE format('REFRESH OBJECT TABLE %s;', full_obj_ident);
    
        -- 3. 刷新 Dynamic Table
        RAISE NOTICE 'Refreshing Dynamic Table: %', image_table_name;
        EXECUTE format('REFRESH TABLE %s;', full_image_table_ident);
    
        RAISE NOTICE 'Refresh image table complete: %', image_table;
    END;
    $$ LANGUAGE plpgsql;
  • 删除存储过程

    CREATE OR REPLACE PROCEDURE drop_image_table(
        image_table TEXT
    )
    AS $$
    DECLARE
        image_schema_name TEXT;
        image_table_name   TEXT;
        obj_table_name TEXT;
        full_image_table_ident TEXT;
        full_obj_ident    TEXT;
        rec RECORD;
    BEGIN
        -- 1. 解析 schema 和表名
        IF position('.' in image_table) > 0 THEN
            image_schema_name := split_part(image_table, '.', 1);
            image_table_name   := split_part(image_table, '.', 2);
        ELSE
            image_schema_name := 'public';
            image_table_name   := image_table;
        END IF;
    
        obj_table_name := image_table_name || '_obj_table';
    
        full_image_table_ident := format('%I.%I', image_schema_name, image_table_name);
        full_obj_ident    := format('%I.%I', image_schema_name, obj_table_name);
    
        -- 2. 删除表
        -- 2.1 关闭动态表自动刷新
        -- RAISE NOTICE 'Disabling auto refresh for %', full_image_table_ident;
        -- EXECUTE format('ALTER TABLE IF EXISTS %s SET (auto_refresh_enable=false)', full_image_table_ident);
    
        -- 2.2 查找 RUNNING 刷新任务并取消
        FOR rec IN
            EXECUTE format(
                $f$
                SELECT query_job_id
                    FROM hologres.hg_dynamic_table_refresh_log(%L)
                    WHERE status = 'RUNNING';
                $f$,
                image_table
            )
        LOOP
            RAISE NOTICE 'Found running refresh job: %', rec.query_job_id;
            IF hologres.hg_internal_cancel_query_job(rec.query_job_id::bigint) THEN
                RAISE NOTICE 'Cancel job % succeeded.', rec.query_job_id;
            ELSE
                RAISE WARNING 'Cancel job % failed.', rec.query_job_id;
            END IF;
        END LOOP;
    
        -- 2.3 删除 Dynamic Table
        RAISE NOTICE 'Dropping Dynamic Table: %', image_table_name;
        EXECUTE format('DROP TABLE IF EXISTS %s;', full_image_table_ident);
    
        -- 2.4 删除 Object Table
        RAISE NOTICE 'Dropping Object Table: %', obj_table_name;
        EXECUTE format('DROP OBJECT TABLE IF EXISTS %s;', full_obj_ident);
    
        RAISE NOTICE 'Drop image table complete: %', image_table;
    END;
    $$ LANGUAGE plpgsql;