在自动驾驶系统中,车辆图片分析是环境感知模块的核心场景之一,主要用于实时解析车辆内外部摄像头捕捉的视觉信息,以实现对周围环境的精准理解与决策。本文以BDD自动驾驶数据集为例模拟自动驾驶场景,模拟真实驾驶场景,采集10万张包括驾驶区域、地理、环境、天气多样性等数据的图片,通过对图片的分析与检索,进行了行驶轨迹分析、环境感知优化及行人车辆识别精度提升等全栈技术验证,提升系统对复杂交通场景的适应能力、安全性和用户体验。
核心能力介绍
本文主要实现对图片的加工与多模检索分析,包含的主要能力如下:
非结构化数据(Object Table):支持通过表的形式读取OSS中非结构化数据(PDF、IMAGE、PPT等)。
AI Function:在Hologres中可以用标准SQL的方式调用Function,自动调用内置大模型,完成AI服务建设场景。
数据加工:提供Embed、Chunk算子,可以对非结构化数据加工成结构化数据存储,无需使用外部算法就能自动Embed。
数据检索和分析:提供
ai_gen
、ai_summarize
等算子,使用SQL就能对数据进行推理、问题总结以及翻译等能力。
Dynamic Table介绍:支持增量刷新模式对非结构化数据自动加工,每次只计算增量的数据有效减少重复计算,降低资源利用率。
向量检索:支持标准SQL的向量检索,用于非结构化数据的相似度搜索、场景识别等,在同一个查询中可以自由地实现向量和标量的检索。
全文检索:通过倒排索引、分词等机制实现对非结构化数据的高效检索,支持关键词匹配、短语检索等丰富的检索方式,实现更加灵活的检索。
方案优势
通过如上核心能力,在Hologres中对图片检索的核心优势如下:
完整的AI数据处理流程:涵盖从数据Embed、Chunk、增量加工和检索/分析的全流程,与使用大数据系统一样轻松构建AI应用。
标准SQL加工图片数据:无需使用专用开发语言,纯SQL就能完成图片数据提取、加工。
一个平台支持跨模态检索:支持以文搜图、以图搜图,语义理解突破关键词局限,在Hologres就能实现跨模态检索。
检索更精准、灵活和智能:可以轻松构建“关键词+语义+多模态”的混合检索链路,覆盖从精准搜索到意图理解的全场景需求。还能结合AI Function实现对用户意图的深度理解,语义关联和上下文推理,实现更智能的检索能力。
数据不出库,安全性更高:不需要将数据导出到外部系统,与Hologres的多种安全能力无缝集成,并高效保护数据安全。
方案流程介绍
本次方案的流程如下:
数据集准备。
将图片数据上传至OSS存储。
图片加工。
使用Object Table读取图片的元数据信息,然后创建增量刷新的Dynamic Table对数据进行Embed,并为Dynamic Table构建向量索引,以便后续检索能够充分利用索引的功能。
使用
ai_embed
算子将自然语言的问题进行Embedding,然后使用向量检索输出Top N的结果。
准备工作
数据准备
本文使用ModelScope公开的BDD100K 自动驾驶图像数据集中
val.zip
文件,模拟多个车辆真是行驶数据。环境准备
购买Hologres V4.0及以上版本实例并创建数据库。
本文以
large-96core-512GB-384GB
、1个节点为例。模型部署。本次方案使用的模型以及分配的资源为:
类别
示例值
模型名称
image_embed
模型类别
clip-ViT-B-32
模型作用描述
图片Embedding
单副本CPU(Core)
7
单副本内存
30 GB
单副本GPU
1卡(96 GB)
资源副本数
1
说明上述模型的资源均为默认分配的资源。
操作步骤
下载图片数据并导入至OSS。
下载BDD100K 自动驾驶图像数据集中的
val.zip
文件。登录OSS管理控制台,创建Bucket并将已下载的
val.zip
文件上传至该Bucket路径下。上传操作详情,请参见简单上传。说明文件夹名称请使用小写。
账号授权。
登录RAM控制台,创建阿里云RAM角色并授予OSS的相关权限。
推荐授予AliyunOSSReadOnlyAccess权限。
为上述阿里云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用户(子账号)
为RAM用户授权。
为已创建的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" }
对图片进行Embedding。
创建Object Table和Dynamic Table读取图片元数据,并对图片加工Embedding。因为流程较长,Hologres直接将过程封装成附录:存储过程。该存储过程包括的能力如下:
创建一张Object Table,用于读取图片的元数据。
创建一张增量刷新的Dynamic Table结果表,用于存储加工后的数据,并设置向量索引。该Dynamic Table未设置自动刷新,需要手动刷新。
Dynamic Table的刷新过程中会使用
ai_embed
对图片进行Embedding。
该存储过程的使用如下:
--存储过程,创建Object Table和Dynamic 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' );
刷新结果表。
通过上述步骤创建的Object Table和Dynamic Table都需要手动刷新,才能完成数据加工。该步骤已被封装为附录:存储过程,该存储过程包括的能力如下:
刷新一次Object Table获取图片元数据。
刷新一次Dynamic Table,进行图片的Embedding加工。
该存储过程的使用如下:
--刷新Dynamic Table,将图片Embedding CALL refresh_image_table( image_table => 'public.dt_image_bdd100k' );
图片检索。
图片数据处理过后可以使用向量检索和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中找到第一个结果的图片如下:
以图搜图
以图搜图的示例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中找到召回的图片,并做对比,结果如下:
附录:存储过程
创建Object Table和Dynamic 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 Table和Dynamic 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;