You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

十万级图像目录中相似图像查找:现成方案及C# OpenCV实现求助

现成解决方案推荐
  • 感知哈希类工具:这类工具通过计算图像的感知哈希值(如dHash、pHash),对比哈希值的汉明距离判断图像相似性,对缩放、格式差异兼容性好,性能适合10万级图像检索。.NET生态里有现成的NuGet包(如ImageHash、ImageSharp结合哈希算法实现),无需复杂的特征点匹配逻辑。
  • 开源图像检索项目:可在代码托管平台搜索关键词“OpenCV image retrieval C#”,找到使用ORB特征(替代需要专利授权的SURF)结合FLANN索引实现的批量检索项目,直接复用即可。
基于OpenCV的C#控制台应用实现

以下是完整实现,采用ORB特征点+FLANN索引完成相似图像检索,适配图像缩放、格式差异场景:

前置准备

安装NuGet包:OpenCvSharp4、OpenCvSharp4.runtime.win(根据操作系统选择对应runtime包)

代码实现

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenCvSharp;
using OpenCvSharp.Flann;

namespace SimilarImageSearch
{
    class Program
    {
        private static Dictionary<string, Mat> _imageDescriptors = new Dictionary<string, Mat>();
        private static Index _flannIndex;

        static void Main(string[] args)
        {
            if (args.Length < 2)
            {
                Console.WriteLine("使用方式:SimilarImageSearch.exe <图像目录路径> <目标图像路径>");
                return;
            }

            string imageDir = args[0];
            string targetImagePath = args[1];

            LoadImageFeatures(imageDir);
            BuildFlannIndex();
            var similarImages = SearchSimilarImages(targetImagePath, topN: 5);

            Console.WriteLine("最相似的5张图像:");
            foreach (var (path, distance) in similarImages)
            {
                Console.WriteLine($"文件名:{Path.GetFileName(path)},匹配距离:{distance:F2}");
            }
        }

        /// <summary>
        /// 加载目录内所有图像的ORB特征描述符
        /// </summary>
        private static void LoadImageFeatures(string imageDir)
        {
            var validExtensions = new[] { ".jpg", ".jpeg", ".png", ".bmp", ".tiff" };
            var imagePaths = Directory.EnumerateFiles(imageDir)
                                      .Where(p => validExtensions.Contains(Path.GetExtension(p).ToLower()));

            using var orb = ORB.Create(500);

            foreach (var path in imagePaths)
            {
                using var img = Cv2.ImRead(path, ImreadModes.Grayscale);
                if (img.Empty())
                {
                    Console.WriteLine($"跳过无法加载的图像:{path}");
                    continue;
                }

                KeyPoint[] keypoints;
                Mat descriptors = new Mat();
                orb.DetectAndCompute(img, null, out keypoints, descriptors);

                if (descriptors.Rows > 0)
                {
                    _imageDescriptors.Add(path, descriptors.Clone());
                }
            }

            Console.WriteLine($"成功加载 {_imageDescriptors.Count} 张图像的特征");
        }

        /// <summary>
        /// 构建FLANN索引实现快速近似检索
        /// </summary>
        private static void BuildFlannIndex()
        {
            var allDescriptors = new Mat();
            foreach (var desc in _imageDescriptors.Values)
            {
                allDescriptors.PushBack(desc);
            }

            var indexParams = new LshIndexParams(6, 12, 1);
            _flannIndex = new Index(allDescriptors, indexParams);
        }

        /// <summary>
        /// 检索与目标图像最相似的图像
        /// </summary>
        private static List<(string Path, float Distance)> SearchSimilarImages(string targetPath, int topN)
        {
            using var orb = ORB.Create(500);
            using var targetImg = Cv2.ImRead(targetPath, ImreadModes.Grayscale);
            if (targetImg.Empty())
            {
                throw new FileNotFoundException("无法加载目标图像", targetPath);
            }

            KeyPoint[] targetKeypoints;
            Mat targetDescriptors = new Mat();
            orb.DetectAndCompute(targetImg, null, out targetKeypoints, targetDescriptors);

            if (targetDescriptors.Rows == 0)
            {
                throw new InvalidOperationException("目标图像无法提取有效特征点");
            }

            var indices = new Mat();
            var distances = new Mat();
            _flannIndex.KnnSearch(targetDescriptors, indices, distances, topN, new SearchParams(50));

            var imageDistanceDict = new Dictionary<string, List<float>>();
            for (int i = 0; i < indices.Rows; i++)
            {
                for (int j = 0; j < indices.Cols; j++)
                {
                    int idx = (int)indices.At<int>(i, j);
                    float dist = distances.At<float>(i, j);

                    var imagePath = _imageDescriptors.Keys.ElementAt(idx / 500);
                    if (!imageDistanceDict.ContainsKey(imagePath))
                    {
                        imageDistanceDict[imagePath] = new List<float>();
                    }
                    imageDistanceDict[imagePath].Add(dist);
                }
            }

            return imageDistanceDict.Select(kv => (kv.Key, kv.Value.Average()))
                                    .OrderBy(item => item.Item2)
                                    .Take(topN)
                                    .ToList();
        }
    }
}

注意事项

  • 首次运行会遍历所有图像生成特征,10万张图像耗时较长,建议将特征描述符序列化存储到本地(如用BinaryWriter),后续直接加载序列化文件避免重复计算。
  • ORB特征为二进制特征,无需专利授权,比SURF更易部署;可根据精度需求调整ORB的特征点数量(如增加到1000)。
  • 匹配距离越小表示图像越相似,可根据实际场景设置阈值过滤结果。

内容的提问来源于stack exchange,提问作者Arimodu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 06:39:13