使用开源PolarDB和imgsmlr进行高效的图片存储和相似度搜索

PolarDB的云原生架构提供高性价比的数据存储、可扩展的灵活性、高效的并行计算能力以及快速的数据搜索和处理能力。PolarDB通过结合计算算法,挖掘业务数据的价值,将其转化为生产力。

本文介绍如何使用开源PolarDB和imgsmlr存储图像特征值,并快速进行图像相似度搜索。

本演示的测试环境为 macOS + Docker。关于部署PolarDB的详细说明,请参考以下文章:

PolarDB的简单部署

原则

有多种方法可以对图像进行数字化,例如将其划分为正方形,并用原色和灰度表示每个正方形。然后将图像逐层逐渐缩小(例如,从 81 个正方形缩小到 9 个正方形)并压缩成一个较小的正方形,形成另一个正方形阵列。N^2N^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 两个特征之间的欧克里底距离

在PolarDB上部署imgsmlr

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)

你可能感兴趣的:(开源)