PolarDB的云原生架构提供高性价比的数据存储、可扩展的灵活性、高效的并行计算能力以及快速的数据搜索和处理能力。PolarDB通过结合计算算法,挖掘业务数据的价值,将其转化为生产力。
本文介绍如何使用开源PolarDB和imgsmlr存储图像特征值,并快速进行图像相似度搜索。
本演示的测试环境为 macOS + Docker。关于部署PolarDB的详细说明,请参考以下文章:
PolarDB的简单部署
有多种方法可以对图像进行数字化,例如将其划分为正方形,并用原色和灰度表示每个正方形。然后将图像逐层逐渐缩小(例如,从 81 个正方形缩小到 9 个正方形)并压缩成一个较小的正方形,形成另一个正方形阵列。N^2
N^2
在图像相似性搜索过程中,将比较两个方形数组之间的向量距离。N^2
通过使用GIST索引接口,可以实现向量相似性搜索的快速收敛。这涉及使用中心点作为存储桶的数据分区,并采用多层缩略图压缩搜索算法(请参阅本文的后面部分)。
本文介绍如何使用开源的PolarDB和imgsmlr来存储图像特征值,并高效进行图像相似度搜索。
1. 介绍两种数据类型:细节向量和特征向量。特征向量占用的空间更小,查询效率更高,通常用于初始数据过滤,而细节向量则用于更细致的过滤。
数据类型 | 存储长度 | 描述 |
模式 | 16388 字节 | Haar小波变换在图像上的结果 |
签名 | 64 字节 | 使用 GiST 索引进行快速搜索的模式的简短表示 |
2、介绍几种图像转换功能接口。
功能 | 返回类型 | 描述 |
jpeg2pattern(bytea) | 模式 | 将jpeg图像转换为图案 |
png2pattern(bytea) | 模式 | 把png图像转换为图案 |
GIF2模式(bytea) | 模式 | 将gif图像转换为图案 |
pattern2signature(模式) | 签名 | 从模式创建签名 |
shuffle_pattern(模式) | 模式 | 随机播放模式,降低对图像偏移的敏感度 |
3. 引入两种向量距离计算算子和索引排序支持。
算子 | 左型 | 正确类型 | 返回类型 | 描述 |
<-> | 模式 | 模式 | 浮点8 | 两种模式之间的欧克里得距离 |
<-> | 签名 | 签名 | 浮点8 | 两个特征之间的欧克里底距离 |
1. 安装 png 和 jpeg 的图片库依赖。
sudo yum install -y libpng-devel
sudo yum install -y libjpeg-turbo-devel
sudo vi /etc/ld.so.conf
# add
/usr/lib64
sudo ldconfig
2. 安装gd库,用于jpeg、png、gif等图片格式的序列化转换。
git clone --depth 1 https://github.com/libgd/libgd
cd libgd/
mkdir build
cd build
cmake -DENABLE_PNG=1 -DENABLE_JPEG=1 ..
make
sudo make install
...
-- Installing: /usr/local/lib64/libgd.so.3.0.16
-- Installing: /usr/local/lib64/libgd.so.3
...
sudo vi /etc/ld.so.conf
# add
/usr/local/lib64
sudo ldconfig
export LD_LIBRARY_PATH=/usr/local/lib64:$LD_LIBRARY_PATH
3. 安装 imgsmlr。
git clone --depth 1 https://github.com/postgrespro/imgsmlr
cd imgsmlr/
USE_PGXS=1 make
USE_PGXS=1 make install
ldd /home/postgres/tmp_basedir_polardb_pg_1100_bld/lib/imgsmlr.so
linux-vdso.so.1 => (0x00007ffc25d52000)
libgd.so.3 => /usr/local/lib64/libgd.so.3 (0x00007fd7a4463000)
libc.so.6 => /lib64/libc.so.6 (0x00007fd7a3ee5000)
libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fd7a3bdd000)
libm.so.6 => /lib64/libm.so.6 (0x00007fd7a38db000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fd7a36c5000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd7a42b3000)
4. 加载插件。
psql
create extension imgsmlr ;
生成测试图像。
cd imgsmlr
USE_PGXS=1 make installcheck
图像导入、矢量化和图像相似性搜索测试。
psql
-- Create a plug-in.
CREATE EXTENSION imgsmlr;
-- Create a table that stores the binary of the original image.
CREATE TABLE image (id integer PRIMARY KEY, data bytea);
-- Create a temporary table for import.
CREATE TABLE tmp (data text);
-- Import images.
\copy tmp from 'data/1.jpg.hex'
INSERT INTO image VALUES (1, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
\copy tmp from 'data/2.png.hex'
INSERT INTO image VALUES (2, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
\copy tmp from 'data/3.gif.hex'
INSERT INTO image VALUES (3, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
\copy tmp from 'data/4.jpg.hex'
INSERT INTO image VALUES (4, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
\copy tmp from 'data/5.png.hex'
INSERT INTO image VALUES (5, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
\copy tmp from 'data/6.gif.hex'
INSERT INTO image VALUES (6, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
\copy tmp from 'data/7.jpg.hex'
INSERT INTO image VALUES (7, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
\copy tmp from 'data/8.png.hex'
INSERT INTO image VALUES (8, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
\copy tmp from 'data/9.gif.hex'
INSERT INTO image VALUES (9, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
\copy tmp from 'data/10.jpg.hex'
INSERT INTO image VALUES (10, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
\copy tmp from 'data/11.png.hex'
INSERT INTO image VALUES (11, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
\copy tmp from 'data/12.gif.hex'
INSERT INTO image VALUES (12, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp));
TRUNCATE tmp;
-- Convert the original image into an image feature vector and an image signature, and import it into a new table.
CREATE TABLE pat AS (
SELECT
id,
shuffle_pattern(pattern)::text::pattern AS pattern,
pattern2signature(pattern)::text::signature AS signature
FROM (
SELECT
id,
(CASE WHEN id % 3 = 1 THEN jpeg2pattern(data)
WHEN id % 3 = 2 THEN png2pattern(data)
WHEN id % 3 = 0 THEN gif2pattern(data)
ELSE NULL END) AS pattern
FROM
image
) x
);
-- Add a PK.
ALTER TABLE pat ADD PRIMARY KEY (id);
-- Create an index in the image signature field.
ALTER TABLE pat ADD PRIMARY KEY (id);
-- Self-correlate and query image similarity (Euclidean distance).
SELECT p1.id, p2.id, round((p1.pattern <-> p2.pattern)::numeric, 4) FROM pat p1, pat p2 ORDER BY p1.id, p2.id;
SELECT p1.id, p2.id, round((p1.signature <-> p2.signature)::numeric, 4) FROM pat p1, pat p2 ORDER BY p1.id, p2.id;
-- Use the index to quickly search for similar images.
SET enable_seqscan = OFF;
SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 1) LIMIT 3;
SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 4) LIMIT 3;
SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 7) LIMIT 3;
SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 10) LIMIT 3;
结果:
SELECT p1.id, p2.id, round((p1.signature <-> p2.signature)::numeric, 4) FROM pat p1, pat p2 ORDER BY p1.id, p2.id;
id | id | round
----+----+--------
1 | 1 | 0.0000
1 | 2 | 0.5914
1 | 3 | 0.6352
1 | 4 | 1.1431
1 | 5 | 1.3843
1 | 6 | 1.5245
1 | 7 | 3.1489
1 | 8 | 3.4192
1 | 9 | 3.4571
1 | 10 | 4.0683
1 | 11 | 3.3551
1 | 12 | 2.4814
2 | 1 | 0.5914
2 | 2 | 0.0000
2 | 3 | 0.7785
2 | 4 | 1.1414
2 | 5 | 1.2839
2 | 6 | 1.4373
2 | 7 | 3.2969
2 | 8 | 3.5381
2 | 9 | 3.5788
2 | 10 | 4.4256
2 | 11 | 3.6138
2 | 12 | 2.7975
3 | 1 | 0.6352
3 | 2 | 0.7785
3 | 3 | 0.0000
3 | 4 | 1.0552
3 | 5 | 1.3885
3 | 6 | 1.4925
3 | 7 | 3.0224
3 | 8 | 3.2555
3 | 9 | 3.2907
3 | 10 | 4.0521
3 | 11 | 3.2095
3 | 12 | 2.4304
4 | 1 | 1.1431
4 | 2 | 1.1414
4 | 3 | 1.0552
4 | 4 | 0.0000
4 | 5 | 0.5904
4 | 6 | 0.7594
4 | 7 | 2.6952
4 | 8 | 2.9019
4 | 9 | 2.9407
4 | 10 | 3.8655
4 | 11 | 2.9710
4 | 12 | 2.1766
5 | 1 | 1.3843
5 | 2 | 1.2839
5 | 3 | 1.3885
5 | 4 | 0.5904
5 | 5 | 0.0000
5 | 6 | 0.7044
5 | 7 | 2.9206
5 | 8 | 3.1147
5 | 9 | 3.1550
5 | 10 | 4.0454
5 | 11 | 3.2023
5 | 12 | 2.3612
6 | 1 | 1.5245
6 | 2 | 1.4373
6 | 3 | 1.4925
6 | 4 | 0.7594
6 | 5 | 0.7044
6 | 6 | 0.0000
6 | 7 | 2.8572
6 | 8 | 3.0659
6 | 9 | 3.1054
6 | 10 | 3.7803
6 | 11 | 2.7595
6 | 12 | 2.0282
7 | 1 | 3.1489
7 | 2 | 3.2969
7 | 3 | 3.0224
7 | 4 | 2.6952
7 | 5 | 2.9206
7 | 6 | 2.8572
7 | 7 | 0.0000
7 | 8 | 0.6908
7 | 9 | 0.7082
7 | 10 | 4.3939
7 | 11 | 3.5039
7 | 12 | 3.2914
8 | 1 | 3.4192
8 | 2 | 3.5381
8 | 3 | 3.2555
8 | 4 | 2.9019
8 | 5 | 3.1147
8 | 6 | 3.0659
8 | 7 | 0.6908
8 | 8 | 0.0000
8 | 9 | 0.0481
8 | 10 | 4.6824
8 | 11 | 3.7398
8 | 12 | 3.5689
9 | 1 | 3.4571
9 | 2 | 3.5788
9 | 3 | 3.2907
9 | 4 | 2.9407
9 | 5 | 3.1550
9 | 6 | 3.1054
9 | 7 | 0.7082
9 | 8 | 0.0481
9 | 9 | 0.0000
9 | 10 | 4.6921
9 | 11 | 3.7523
9 | 12 | 3.5913
10 | 1 | 4.0683
10 | 2 | 4.4256
10 | 3 | 4.0521
10 | 4 | 3.8655
10 | 5 | 4.0454
10 | 6 | 3.7803
10 | 7 | 4.3939
10 | 8 | 4.6824
10 | 9 | 4.6921
10 | 10 | 0.0000
10 | 11 | 1.8252
10 | 12 | 2.0838
11 | 1 | 3.3551
11 | 2 | 3.6138
11 | 3 | 3.2095
11 | 4 | 2.9710
11 | 5 | 3.2023
11 | 6 | 2.7595
11 | 7 | 3.5039
11 | 8 | 3.7398
11 | 9 | 3.7523
11 | 10 | 1.8252
11 | 11 | 0.0000
11 | 12 | 1.2933
12 | 1 | 2.4814
12 | 2 | 2.7975
12 | 3 | 2.4304
12 | 4 | 2.1766
12 | 5 | 2.3612
12 | 6 | 2.0282
12 | 7 | 3.2914
12 | 8 | 3.5689
12 | 9 | 3.5913
12 | 10 | 2.0838
12 | 11 | 1.2933
12 | 12 | 0.0000
(144 rows)
postgres=# SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 1) LIMIT 3;
id
----
1
2
3
(3 rows)
postgres=# SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 4) LIMIT 3;
id
----
4
5
6
(3 rows)
postgres=# SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 7) LIMIT 3;
id
----
7
8
9
(3 rows)
postgres=# SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 10) LIMIT 3;
id
----
10
11
12
(3 rows)
postgres=# explain SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 10) LIMIT 3;
QUERY PLAN
------------------------------------------------------------------------------------
Limit (cost=8.29..10.34 rows=3 width=8)
InitPlan 1 (returns $0)
-> Index Scan using pat_pkey on pat pat_1 (cost=0.14..8.15 rows=1 width=64)
Index Cond: (id = 10)
-> Index Scan using pat_signature_idx on pat (cost=0.13..8.37 rows=12 width=8)
Order By: (signature <-> $0)
(6 rows)