枪与玫瑰

PDO(mysql驱动)查询超时设置方法

PHP PDO 超时设置误区与解决方案

一、官方说明

PDO::ATTR_TIMEOUT 参数用于设置超时时间(单位:秒),但不同数据库驱动的行为存在差异:

  • SQLite:等待可写锁的最长时间
  • MySQL:默认作为连接超时时间
  • 其他驱动:可能解释为读取超时或连接超时

注意:该参数为 int 类型,且部分驱动可能不支持


二、常见误区解析

1. 连接初始化后设置超时

$db = new PDO($dsn, $user, $pass);
$db->setAttribute(PDO::ATTR_TIMEOUT, 1); // 无效操作

问题本质
PDO 构造函数已开始建立连接,此时修改超时参数对正在进行的连接无影响
表现:仍使用默认 30 秒超时


2. 试图通过 default_socket_timeout 控制

ini_set('default_socket_timeout', 3);
$db = new PDO($dsn, $user, $pass); // 仍使用默认 30 秒

问题根源
PDO 源码(以 PHP 5.6.29 为例)中硬编码了默认超时:

long connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30 TSRMLS_CC);

表现:socket 超时设置被源码默认值覆盖


3. 误用 ATTR_TIMEOUT 控制查询超时

$db = new PDO($dsn, $user, $pass, [PDO::ATTR_TIMEOUT => 1]);
$db->query('SELECT SLEEP(5)'); // 实际等待 5 秒

核心矛盾
官方注释明确说明 PDO_ATTR_TIMEOUT 仅控制连接超时:

PDO_ATTR_TIMEOUT, /* connection timeout in seconds */

表现:查询超时由驱动单独控制,与该参数无关

《高性能mysql(第三版)》简读-索引部分

数据库索引优化指南

5.3 创建高性能的索引

一、索引类型

1. B-Tree 索引

  • 特点:对索引列顺序组织存储,适合查找范围数据
  • 支持的查询
    • 全值匹配
    • 匹配最左前缀
    • 匹配列前缀
    • 匹配范围值
    • 精确匹配某一列并范围匹配另一列
    • 只访问索引的查询
    • 排序
  • 限制
    • 非最左列不匹配
    • 不能跳过索引中的列
    • 某列范围查询时,其余索引列无法使用

2. 哈希索引

  • 特点:只有精确匹配索引所有列的查询才有效
  • 优势
    • 索引只存储哈希值,结构紧凑,查询速度快
  • 限制
    • 不存储索引值,需要读取行
    • 无法用于排序
    • 不支持部分索引列匹配
    • 只支持等值比较查询
    • 可能发生哈希冲突
    • 哈希冲突较多时,索引维护操作代价高
  • 备注:InnoDB内部会自动创建自适应哈希索引

3. R-Tree 索引

  • 专为多维数据(如空间数据)设计,适合地理空间查询

4. 全文索引

  • 专为文本搜索优化,支持全文检索和模糊匹配

二、高性能索引策略

1. 独立的列

  • 索引列不能是表达式的部分
  • 例如:WHERE column = value 而不是 WHERE column + 1 = value

2. 前缀索引和索引的选择性

  • 适用于很长的字符列(BLOB、TEXT或很长的varchar)
  • 优点:索引更小,检索速度更快
  • 缺点:无法使用前缀索引做GROUP BY、ORDER BY和覆盖扫描

3. 多列索引

  • 使用场景
    • 当查询需要对多个索引做相交操作时,使用包含所有相关列的多列索引
    • 当查询需要多个索引做联合操作(EXPLAIN的Extra包含Using union)时,可能耗费更多CPU和内存资源
    • EXPLAIN的type列中包含index_merge表示使用了索引合并
  • 优化建议:使用IGNORE INDEX让优化器忽略某些索引

4. 选择合适的索引列顺序(B-Tree)

  • 经验法则:将选择性最高的列放在前面
  • 备注:经验法则考虑的是全局基数和选择性,实际使用中需考虑表中值的分布情况

5. 聚簇索引

  • 特点
    • 如果没有定义主键,InnoDB会选择一个唯一的非空索引代替
    • 如果没有这样的索引,InnoDB会隐式定义一个主键作为聚簇索引
  • 优点
    • 相关数据保存在一起
    • 数据访问更快(索引和数据保存在同一个B-Tree中)
    • 覆盖索引扫描查询可直接使用页节点中的主键值
  • 缺点
    • 仅在I/O密集型应用中提高性能
    • 插入速度严重依赖插入顺序
    • 更新聚簇索引列代价高
    • 可能面临"页分裂"问题
    • 可能导致全表扫描变慢

三、顺序主键

1. 非循序主键问题

  • 问题
    • 写入目标页可能已从缓存中移除,导致大量随机I/O
    • 乱序写入导致页分裂,需要移动大量数据
    • 页分裂导致数据碎片
  • 解决方案:使用OPTIMIZE TABLE重建表并优化页填充

2. 顺序主键问题

  • 问题
    • 并发插入可能导致主键上界成为"热点",出现间隙锁竞争
    • AUTO_INCREMENT的锁机制成为"热点"

四、覆盖索引

  • 定义:一个索引包含所有需要查询的字段值
  • 特点
    • 查询字段按需读取,当所有字段都包含在索引中时,存储引擎无需回表查找行(减少随机I/O)
    • MySQL在索引中不能做以通配符开头的LIKE查询
    • EXPLAIN的Extra列包含"Using index"表示为索引覆盖查询

五、使用索引扫描来做排序

  • 特点
    • EXPLAIN的type列包含index表示使用了索引扫描来做排序
    • 仅当索引列顺序和ORDER BY顺序完全一致,且所有列排序方向相同时,才能使用索引排序
    • 如果前导列为常量(如WHERE a='1'),则ORDER BY子句可以不满足索引最左前缀要求

六、冗余和重复索引

  • 特点
    • InnoDB主键列已包含在二级索引中,通常无需重复包含主键
    • 冗余索引用于满足不同查询场景,但会影响写操作性能

七、未使用的索引

  • 检测方法
    • Percona Server或MariaDB:打开userstates服务器变量,运行后查询INFORMATION_SCHEMA.INDEX_STATISTICS
    • Percona Toolkit:使用pt-index-usage工具读取查询日志分析索引使用情况

八、索引和锁

  • 特点
    • EXPLAIN的Extra列包含"Using where"表示存储引擎返回行后应用WHERE过滤条件
    • 索引可以减少需要锁定的行数

九、索引案例学习

  • 优化建议
    • 尽可能将需要范围查询的列放到索引后面,以便优化器使用尽可能多的索引列
    • 对于范围条件查询,MySQL无法使用范围列后面的其他索引列
    • 对于多等值查询(如WHERE a=1 AND b=2),则无此限制

关键总结:在设计数据库索引时,应根据实际查询模式和数据分布情况,选择合适的索引类型和结构,避免冗余索引,确保查询性能和写入性能的平衡。

Opencv Install With Python

OpenCV 2.4.13 在 CentOS 系统上的安装指南

一、安装系统依赖

# 确保GCC版本 >= 4.8.0 (CentOS 6.5默认GCC版本较低,需升级)
yum install gcc

# 安装CMake 3.0.0+ (CentOS 6.5默认版本较低)
yum install cmake

# 安装GTK+和相关开发库
yum install gtk*

# 安装pkg-config
yum install pkgconfig

# 安装Python开发包
yum install python-dev*

# 安装NumPy
yum install numpy
pip install numpy

重要提示:确保Python版本与系统环境匹配,建议使用Python 2.7.x。

二、下载OpenCV源码

# 克隆OpenCV 2.4分支
git clone https://github.com/Itseez/opencv.git
git checkout 2.4
cd opencv

三、编译与安装

# 创建构建目录并进入
mkdir build
cd build

# 执行CMake配置
cmake \
-D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D BUILD_EXAMPLES=ON \
-D BUILD_NEW_PYTHON_SUPPORT=ON \
-D INSTALL_PYTHON_EXAMPLES=ON \
-D PYTHON_EXECUTABLE=/usr/local/bin/python2.7 \
-D PYTHON_INCLUDE_DIR=/usr/local/include/python2.7 \
-D PYTHON_LIBRARY=/usr/local/lib/libpython2.7.so.1.0 \
-D PYTHON_NUMPY_INCLUDE_DIR=/usr/local/lib/python2.7/site-packages/numpy/core/include \
-D PYTHON_PACKAGES_PATH=/usr/local/lib/python2.7/site-packages \
..

# 编译并安装
make -j$(nproc)
make install

CMake参数说明

图像识别:图片特征检测

计算机视觉特征检测与匹配指南

一、简介

特征检测是计算机视觉和图像处理中的核心概念,指通过计算机提取图像信息,判断图像上的点是否属于特定特征。特征检测的结果是将图像上的点分为不同的子集,这些子集通常表现为孤立的点、连续的曲线或连续的区域。

特征匹配分为两个关键步骤:

  1. 图片特征检测
  2. 图片特征匹配

二、图片特征检测

算法选择与比较

OpenCV官方文档提供了详细的特征检测算法介绍:

  1. 特征检测文档
  2. 特征检测与描述文档

算法比较

算法 特点 适用场景
SIFT 高精度,计算较慢 需要高精度匹配的场景
SURF 比SIFT更快,精度略低 需要速度的实时应用

简单总结:SURF比SIFT更快,但损失部分准确性。

http://wiki.corp.mama.cn/download/attachments/74515171/image2016-10-9%2014%3A38%3A9.png

三、图片特征匹配

算法选择与比较

OpenCV官方文档提供了特征匹配算法的详细介绍:

算法比较

算法 特点 适用场景
BFMatcher (Brute-Force) 全面检测,精确但速度慢 小规模数据集匹配
FLANN (Fast Library for Approximate Nearest Neighbors) 相邻匹配,速度快 大规模数据集匹配,数据量越大效果越明显

简单总结:BF为全面检测,FLANN为相邻匹配,数据量大的情况下FLANN更快,量越大效果越明显。

http://wiki.corp.mama.cn/download/attachments/74515171/image2016-10-9%2011%3A19%3A33.png

四、实践建议

  1. 小规模数据集:优先使用BFMatcher,确保匹配精度
  2. 大规模数据集:优先使用FLANN,提高匹配效率
  3. 实时应用:考虑使用SURF算法进行特征检测,平衡速度与精度
  4. 高精度要求:使用SIFT算法进行特征检测,确保匹配质量

注意:在实际应用中,建议根据具体场景和性能需求调整参数,可以参考OpenCV官方文档进行优化配置。

「通信安全」对称与非对称加密

加密算法对比与跨平台实现指南

一、HTTPS与加密算法基础

1. HTTPS核心机制

  • 加密实现:结合非对称加密(密钥交换)与对称加密(数据传输)
  • 安全风险:存在中间人劫持风险(需受客户端信任的证书防御)
  • 最佳实践

    为同时兼顾安全性及性能,建议采用由非对称加密进行对称加密密钥的传输,定期更换对称密钥(对称加密在当前计算水平上只能保证在一段时间内是安全的),使用对称加密完成所有数据的加密传输。

二、非对称加密详解

特性分析

特性 说明
安全性 数据加密安全性最高(基于数学难题)
适用场景 仅适合小数据加密(如密钥交换、数字签名)
性能 加解密耗时较高(计算密集型)

典型应用场景

1. 加密传输数据

  • 工作原理:A、B双方各持有对方公钥(公钥公开),使用对方公钥加密数据,仅对方私钥可解密
  • 示例:A用B的公钥加密消息→B用私钥解密

2. 防止数据被伪造

  • 工作原理:A、B双方各持有对方公钥,使用各自私钥加密数据,仅公钥可解密
  • 示例:A用私钥签名→B用A公钥验证

3. 加密并防止数据伪造(双重保障)

  • 方案一(公钥公开):
    1. 使用私钥加密(防篡改)
    2. 使用对方公钥加密(防窃听)
  • 方案二(公钥不公开):
    1. 使用私钥加密(防篡改)
    2. 无需额外加密(公钥不公开,无法伪造)

三、对称加密详解

核心特性

特性 说明
安全性 数据加密安全性较高
适用场景 适合大量数据加密(如文件传输、数据库加密)
性能 加解密耗时较少(计算高效)

典型应用场景

  • 加密并防止传输数据伪造
  • 对大量数据进行加密

跨平台兼容方案

参数
算法 AES-256-CBC
填充方案 PKCS7Padding
密钥派生 PBKDF2WithHmacSHA1
迭代次数 10,000次
密钥长度 256位

兼容性说明:经实践验证,此组合能同时兼容PHP、iOS、Android、Java平台。

四、多平台对称加密实现

1. PHP实现

/**
 * 对称加密算法类
 */
class SymmetricEncryption{
    const CIPHER = MCRYPT_RIJNDAEL_128;
    const MODEL = MCRYPT_MODE_CBC;
    const HASH_ALGORITHM = 'sha1';
    // 对应android、iOS的{0,1,2,3,4,5,6,7,8,9,0xA,0xB,0xC,0xD,0xE,0xF}
    const HASH_SALT = 'AAECAwQFBgcICQoLDA0ODw==';
    const HASH_ITERATIONS = 10000;
    // 对应android、iOS的 KEY_LENGTH/8
    const HASH_KEY_LENGTH = 32;
    // 对应android、iOS的{0xA,1,0xB,5,4,0xF,7,9,0x17,3,1,6,8,0xC,0xD,91}
    const IV = 'CgELBQQPBwkXAwEGCAwNWw==';

    /**
     * 加密
     * @param $key string 加密密钥
     * @param $data string 待加密明文
     * @return string 密文
     */
    public static function encrypt($key, $data){
        // 对应安卓和iOS的PBKDF2WithHmacSHA1
        $key = hash_pbkdf2(
            self::HASH_ALGORITHM,
            $key,
            base64_decode(self::HASH_SALT),
            self::HASH_ITERATIONS,
            self::HASH_KEY_LENGTH,
            true
        );
        // 使用PKCS7Padding的方式进行填充
        $data = self::PKCS7Padding($data);
        # 创建和 AES 兼容的密文(Rijndael 分组大小 = 256)
        $cipherText = mcrypt_encrypt(
            self::CIPHER,
            $key,
            $data,
            self::MODEL,
            base64_decode(self::IV)
        );
        # 对密文进行 base64 编码便于显性复制
        $cipherTextBase64 = base64_encode($cipherText);
        return $cipherTextBase64;
    }

    /**
     * 解密
     * @param $key string 解密密钥
     * @param $data string 待解密密文
     * @return string 明文
     */
    public static function decrypt($key, $data){
        // 对应安卓和iOS的PBKDF2WithHmacSHA1
        $key = hash_pbkdf2(
            self::HASH_ALGORITHM,
            $key,
            base64_decode(self::HASH_SALT),
            self::HASH_ITERATIONS,
            self::HASH_KEY_LENGTH,
            true
        );
        # --- 解密 ---
        $cipherTextDec = base64_decode($data);
        $plaintextDec = mcrypt_decrypt(
            self::CIPHER,
            $key,
            $cipherTextDec,
            self::MODEL,
            base64_decode(self::IV)
        );
        // 移除PKCS7Padding填充
        $plaintextDec = self::PKCS7RemovePadding($plaintextDec);
        return $plaintextDec;
    }

    /**
     * PKCS7Padding模式填充
     * @param $data
     * @return string
     */
    public static function PKCS7Padding($data){
        $blockSize = mcrypt_get_block_size(self::CIPHER,self::MODEL);
        $pad = $blockSize - (strlen($data) % $blockSize);
        return $data . str_repeat(chr($pad), $pad);
    }

    /**
     * 移除PKCS7Padding模式填充
     * @param $data
     * @return string
     */
    public static function PKCS7RemovePadding($data){
        $pad = ord($data[strlen($data) - 1]);
        return substr($data, 0, -$pad);
    }
}

2. iOS实现

static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
const NSUInteger kAlgorithmKeySize = kCCKeySizeAES256;
 
const NSUInteger kPBKDFRounds = 10000;  // ~80ms on an iPhone 4
 
 
static Byte saltBuff[] = {0,1,2,3,4,5,6,7,8,9,0xA,0xB,0xC,0xD,0xE,0xF};
 
 
static Byte ivBuff[]   = {0xA,1,0xB,5,4,0xF,7,9,0x17,3,1,6,8,0xC,0xD,91};
 
 
@implementation NSData (AES256)
 
+ (NSData *)AESKeyForPassword:(NSString *)password{
    NSMutableData *derivedKey = [NSMutableData dataWithLength:kAlgorithmKeySize];
    NSData *salt = [NSData dataWithBytes:saltBuff length:kCCKeySizeAES128];
    int result = CCKeyDerivationPBKDF(kCCPBKDF2,
                                  password.UTF8String,
                                  password.length,
                                  salt.bytes,
                                  salt.length,
                                  kCCPRFHmacAlgSHA1,
                                  kPBKDFRounds,
                                  derivedKey.mutableBytes,
                                  derivedKey.length);
    NSAssert(result == kCCSuccess,
             @"Unable to create AES key for spassword: %d", result);
    return derivedKey;
}
 
/*加密方法*/
 
+ (NSString *)AES256EncryptWithPlainText:(NSString *)plain key:(NSString *)key {
    NSData *plainText = [plain dataUsingEncoding:NSUTF8StringEncoding];
    char keyPtr[kCCKeySizeAES256+1];
    bzero(keyPtr, sizeof(keyPtr));
    NSUInteger dataLength = [plainText length];
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    bzero(buffer, sizeof(buffer));
    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                          [[NSData AESKeyForPassword:key] bytes], kCCKeySizeAES256,
                                          ivBuff,
                                          [plainText bytes], dataLength,
                                          buffer, bufferSize,
                                          &numBytesEncrypted);
 
    if (cryptStatus == kCCSuccess) {
        NSData *encryptData = [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
        return [encryptData base64Encoding];
    }
 
    free(buffer);
    return nil;
}
 
/*解密方法*/
 
+ (NSString *)AES256DecryptWithCiphertext:(NSString *)ciphertexts key:(NSString *)key{
    NSData *cipherData = [NSData dataWithBase64EncodedString:ciphertexts];
    char keyPtr[kCCKeySizeAES256+1];
    bzero(keyPtr, sizeof(keyPtr));
    NSUInteger dataLength = [cipherData length];
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                          [[NSData AESKeyForPassword:key] bytes], kCCKeySizeAES256,
                                          ivBuff,
                                          [cipherData bytes], dataLength,
                                          buffer, bufferSize,
                                          &numBytesDecrypted);
    if (cryptStatus == kCCSuccess) {
        NSData *encryptData = [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
        return [[NSString alloc] initWithData:encryptData encoding:NSUTF8StringEncoding];
    }
 
    free(buffer);
    return nil;
}
@end

3. Android实现

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
   private final String KEY_GENERATION_ALG = "PBKDF2WithHmacSHA1";
   private final int HASH_ITERATIONS = 10000;
   private final int KEY_LENGTH = 256;

    private byte[] salt = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD,
            0xE, 0xF };
    private byte[] iv = { 0xA, 1, 0xB, 5, 4, 0xF, 7, 9, 0x17, 3, 1, 6, 8, 0xC,
            0xD, 91 };
    private final String CIPHERMODEPADDING = "AES/CBC/PKCS7Padding";
    private PBEKeySpec myKeyspec = null;
    private SecretKeyFactory keyfactory = null;
    private SecretKey sk = null;
    private SecretKeySpec skforAES = null;
    private IvParameterSpec IV;

    public AES(String password) {
        try {
            myKeyspec = new PBEKeySpec(password.toCharArray(), salt,
                    HASH_ITERATIONS, KEY_LENGTH);
            keyfactory = SecretKeyFactory.getInstance(KEY_GENERATION_ALG);
            sk = keyfactory.generateSecret(myKeyspec);
        } catch (Exception ex) {
            System.err.println("Encryption Exception: " + ex.toString());
        }

        byte[] skAsByteArray = sk.getEncoded();
        skforAES = new SecretKeySpec(skAsByteArray, "AES");
        IV = new IvParameterSpec(iv);
    }

    public String encrypt(byte[] plaintext) {
        byte[] ciphertext = encrypt(CIPHERMODEPADDING, skforAES, IV, plaintext);
        String base64_ciphertext = Base64Encoder.encode(ciphertext);
        return base64_ciphertext;
    }

    public String decrypt(String ciphertext_base64) {
        byte[] s = Base64Decoder.decodeToBytes(ciphertext_base64);
        String decrypted = new String(decrypt(CIPHERMODEPADDING, skforAES, IV, s));
        return decrypted;
    }

    private byte[] encrypt(String cmp, SecretKey sk, IvParameterSpec IV, byte[] msg) {
        try {
            Cipher c = Cipher.getInstance(cmp);
            c.init(Cipher.ENCRYPT_MODE, sk, IV);
            return c.doFinal(msg);
        } catch (Exception ex) {
            System.err.println("Encryption Exception: " + ex.toString());
        }
        return null;
    }

    private byte[] decrypt(String cmp, SecretKey sk, IvParameterSpec IV, byte[] ciphertext) {
        try {
            Cipher c = Cipher.getInstance(cmp);
            c.init(Cipher.DECRYPT_MODE, sk, IV);
            return c.doFinal(ciphertext);
        }catch (Exception ex) {
            System.err.println("Decryption Exception: " + ex.toString());
        }
        return null;
    }
}

五、关键实现差异说明

重要说明:PHP官方文档中使用对称加密的方式是生成随机的IV并将其合并到加密数据之前一起传输(优点是相同数据每次加密结果不同),而Android和iOS的做法是使用固定salt将对称密钥转换为AES密钥后作为实际加解密的密钥。

算法实践-图片特征检测

基于图片特征的相似度检测算法实践

运行环境

  • Python版本:Python 2.7+
  • OpenCV版本:OpenCV 2.4.12
  • 依赖库
    numpy==1.11.0
    PIL==1.1.7
    cv2==2.4.12
    

核心功能

本代码实现了一套基于图片特征的相似度检测算法,通过提取图片特征并进行匹配,实现图片相似度的自动检测。主要功能包括:

  • 多种特征检测算法支持(SURF/SIFT/ORB/BRISK)
  • 多种匹配算法支持(FLANN/BF)
  • 图片特征提取与存储
  • 图片特征匹配与可视化
  • 网络图片加载支持

算法原理

特征检测算法选择

算法 特点 适用场景
SURF 比SIFT更快,但准确性稍低 大规模图片处理
SIFT 准确性高,但计算速度慢 精确匹配场景
ORB 速度较快,适用于实时处理 嵌入式系统/移动设备
BRISK 比ORB更快,精度略低 速度要求高的场景

匹配算法选择

算法 特点 适用场景
FLANN 相邻匹配,大数据量下更快 大规模图片库匹配
BF 全面检测,精度高 小规模图片匹配

代码结构解析

核心类

class FeatureBasedImageMatch:  # 主要匹配类
    def __init__(self, detector='SURF', matcher='FLANN')  # 初始化
    def detect_and_compute(self, image=None, image_file=None)  # 特征提取
    def many_image_match(self, descriptors, ratio=0.75)  # 多图片匹配
    def image_match(self, descriptors_1, descriptors_2, ratio=0.75)  # 两图匹配
    def add_matches_image(self, descriptors)  # 添加图片特征
    def clear_matches_image(self)  # 清空特征

class ImageMatch:  # 图片处理工具类
    def __init__(self):  # 初始化
    def image_read(self, image_file=None, image_buffer=None)  # 图片读取
    def filter_matches(self, kp1, kp2, matches, ratio=0.75)  # 匹配过滤
    def explore_match(self, win, img1, img2, kp_pairs, status=None, H=None)  # 匹配可视化
    def feature_save/load/remove(self, picture_md5)  # 特征文件操作

class timer:  # 计时器类
    def __init__(self, func=time.time)  # 初始化
    def start/stop/reset()  # 计时控制

关键实现

特征提取与匹配流程

graph TD
    A[原始图片] --> B(特征检测)
    B --> C{选择算法}
    C -->|SURF/SIFT| D[计算特征描述符]
    C -->|ORB/BRISK| E[计算特征描述符]
    D/E --> F[特征匹配]
    F --> G{匹配算法}
    G -->|FLANN| H[快速匹配]
    G -->|BF| I[精确匹配]
    H/I --> J[结果筛选]
    J --> K[相似度评估]

关键方法说明

特征提取

def detect_and_compute(self, image=None, image_file=None):
    """提取图片特征"""
    # 读取图片
    image = ImageMatch.image_read(image_file) if not isinstance(image, numpy.ndarray) else image
    
    # 使用指定算法提取特征
    key_points, descriptors = self.detector.detectAndCompute(image, None)
    return key_points, descriptors

特征匹配

def image_match_explore(self, key_points_1, descriptors_1, key_points_2, descriptors_2, ratio=0.75):
    """两图特征匹配可视化"""
    matches = self.matcher.knnMatch(descriptors_1, trainDescriptors=descriptors_2, k=2)
    points_1, points_2, matches = ImageMatch.filter_matches(
        key_points_1, key_points_2, matches, ratio)
    return points_1, points_2, matches

匹配结果可视化

def explore_match(win, img1, img2, kp_pairs, status=None, H=None):
    """可视化匹配结果"""
    # 创建可视化图像
    vis = numpy.zeros((max(h1, h2), w1 + w2), numpy.uint8)
    vis[:h1, :w1] = img1
    vis[:h2, w1:w1 + w2] = img2
    
    # 绘制匹配点
    for (x1, y1), (x2, y2), inlier in zip(p1, p2, status):
        if inlier:
            cv2.line(vis, (x1, y1), (x2, y2), green)
        else:
            # 绘制不匹配点
            ...
            
    cv2.imshow(win, vis)
    cv2.waitKey()

使用示例

# 初始化匹配对象
match_object = FeatureBasedImageMatch()

# 加载待检测图片
match_image = 'http://example.com/image.jpg'
match_image_object = ImageMatch.image_read(match_image)
match_key_point, match_descriptors = match_object.detect_and_compute(image=match_image_object)

# 加载参考图片
images = [
    'http://example.com/ref_image1.jpg',
    'http://example.com/ref_image2.jpg'
]

# 提取参考图片特征
for x in images:
    image_object[x] = {
        'image': ImageMatch.image_read(x),
        'key_points': None,
        'descriptors': None
    }
    image_object[x]['key_points'], image_object[x]['descriptors'] = match_object.detect_and_compute(
        image=image_object[x]['image'])

# 添加参考图片特征到匹配器
for x in images:
    match_object.add_matches_image(image_object[x]['descriptors'])

# 进行匹配
matches = match_object.many_image_match(match_descriptors)

# 可视化匹配结果
ImageMatch.explore_match('match_result', match_image_object, 
                        image_object[images[0]]['image'], matches)

注意事项

  1. OpenCV版本兼容性