ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于MATLAB的Hu不变矩图像检索系统实现与优化

基于MATLAB的Hu不变矩图像检索系统实现与优化 简介基于Hu不变矩的图像检索系统是一套完整的Matlab仿真工程适合图像处理、计算机视觉方向的学生与研究者开展特征提取与检索实验。项目采用经典Hu矩描述图像形状特征并在GUI界面中完成查询、匹配与结果展示能够直观对比不同图像的相似度。压缩包内共69个文件包括55张jpg图像库、3张bmp测试图、7个m脚本、1个fig界面文件、1个mat数据文件及1个avi操作演示视频整体大小仅1.53MB核心代码覆盖完整流程MainForm.fig与MainForm.m构成交互界面get_hu_vec.m计算Hu不变矩GetDatabaseVec.m生成图像库特征向量SearchResult.m与InitAxes.m实现相似度排序和结果显示配合演示视频可快速复现检索效果。目前已有695人在CSDN学习使用该资源适合需要上手GUI图像检索项目或掌握Hu不变矩应用的开发者参考。1. 为什么还在拿Hu不变矩做图像检索一个2021a版本MATLAB项目核心算法还是上世纪60年代提出的Hu不变矩听起来确实不够“前沿”。但打开这个带GUI的工程后我得说它反而是最适合拿来理解图像检索全流程的样本没有深度学习环境依赖、不需要GPU、50张左右的图片库几分钟就能跑完却能完整走通“特征提取—特征库构建—相似度计算—结果排序展示”这条主流链路。系统入口是Runme.m界面文件MainForm.fig与MainForm.m配套另有37、38张标准测试图片和操作录像0015.avi对课程设计、毕设预研或者想快速验证检索思路的人来说骨架非常干净。需要留意的是环境必须是MATLAB 2021a或更高版本运行时当前文件夹必须指向工程所在路径禁止直接运行子函数文件——这几个约束我在下文会逐个解释为什么踩得上。2. Hu不变矩的7个特征量与MATLAB实现2.1 平移、旋转、尺度不变性成立的前提Hu不变矩的核心思想是把二值或灰度图像的像素分布映射到一组统计矩上这组矩对平移、旋转和尺度变化保持数值稳定。对于一张灰度图 (I(x,y))定义 ((pq)) 阶原点矩 (m_{pq}\sum_x\sum_y x^p y^q I(x,y))再通过质心 (( \bar{x}, \bar{y} )(m_{10}/m_{00}, m_{01}/m_{00})) 构造中心矩[ \mu_{pq}\sum_x\sum_y (x-\bar{x})^p (y-\bar{y})^q I(x,y) ]把中心矩用 (\mu_{00}^{(pq)/21}) 归一化得到归一化中心矩 (\eta_{pq})。Hu在1962年用二阶和三阶归一化中心矩组合出了7个不变矩前6个对镜像也保持数值不变第7个能区分镜像。注意“不变”是有条件的图像内容必须是同一目标只是发生了刚体变换如果视角变了、遮挡了、光照变了矩值依然会漂。这个项目里的图片库包含柏林城市景观、斯巴鲁赛车、啤酒瓶、专辑封面等完全不同的类别类间差异远大于同一物体变换带来的矩值漂移所以检索能成立。2.2 get_hu_vec.m 的核心计算工程根目录下的 get_hu_vec.m 负责把一张图像转成 1×7 的向量常见实现是先调用 MATLAB 的hu函数或自写 CenterMoment 计算中心矩再套公式。下面这段等价实现可以直接替换原文件结果一致function hu_vec get_hu_vec(I) if size(I, 3) 3 I rgb2gray(I); % Hu矩基于灰度或二值图 end I im2double(I); [m, n] size(I); % 0阶矩像素总能量 m00 sum(I(:)); % 质心 m10 sum(sum(I) .* (1:n)); m01 sum(I .* (1:m)); xc m10 / m00; yc m01 / m00; % 中心矩 [X, Y] meshgrid(1:n, 1:m); Xc X - xc; Yc Y - yc; mu20 sum(sum(I .* Xc.^2)); mu02 sum(sum(I .* Yc.^2)); mu11 sum(sum(I .* Xc .* Yc)); mu30 sum(sum(I .* Xc.^3)); mu03 sum(sum(I .* Yc.^3)); mu12 sum(sum(I .* Xc .* Yc.^2)); mu21 sum(sum(I .* Xc.^2 .* Yc)); % 归一化中心矩 n20 mu20 / m00^2; n02 mu02 / m00^2; n11 mu11 / m00^2; n30 mu30 / m00^2.5; n03 mu03 / m00^2.5; n12 mu12 / m00^2.5; n21 mu21 / m00^2.5; % 7个Hu不变矩 hu_vec(1) n20 n02; hu_vec(2) (n20 - n02)^2 4*n11^2; hu_vec(3) (n30 - 3*n12)^2 (3*n21 - n03)^2; hu_vec(4) (n30 n12)^2 (n21 n03)^2; hu_vec(5) (n30 - 3*n12)*(n30 n12) * ... ((n30 n12)^2 - 3*(n21 n03)^2) ... (3*n21 - n03)*(n21 n03) * ... (3*(n30 n12)^2 - (n21 n03)^2); hu_vec(6) (n20 - n02) * ... ((n30 n12)^2 - (n21 n03)^2) ... 4*n11*(n30 n12)*(n21 n03); hu_vec(7) (3*n21 - n03)*(n30 n12) * ... ((n30 n12)^2 - 3*(n21 n03)^2) - ... (n30 - 3*n12)*(n21 n03) * ... (3*(n30 n12)^2 - (n21 n03)^2); hu_vec hu_vec(:);meshgrid生成坐标网格后所有矩运算都是矩阵点乘避免双重 for 循环。m00^2对二阶矩做尺度归一化m00^2.5对应三阶矩这样同一物体放大缩小后矩值不变。有一点要特别注意如果原图是纯色背景或大面积平坦区域(\mu_{00}) 可能接近 0除出来就溢出所以工程在抽取前对图像做的是全图矩计算没有做前景分割——这恰好是 Hu 矩检索最薄弱的环节碰到背景复杂的照片7 个矩里后几个会抖动得比较厉害。2.3 矩值量纲差异必须做对数压缩直接拿上面算出的 7 个矩去做距离度量会出问题一阶矩组合的量级通常在 (10^{-1})而三阶矩组合可能到 (10^{-4}) 甚至更小欧氏距离会被前两维主导后面的矩等于白算。常见的做法是对矩值取带符号对数hu_log sign(hu_vec) .* log10(abs(hu_vec) eps);这样一来所有维度基本压缩到 ([-6, 0]) 区间每个矩对距离都有话语权。工程里 H.mat 保存的特征向量一般就是压缩后的版本——如果发现检索结果总把大面积亮色图片归为一类先去查这一步有没有做。下表汇总 7 个矩各自对形状的敏感点方便定位匹配失败时是哪一维在拖后腿矩索引主要捕获受干扰因素1整体散布程度背景亮色区域2长轴与短轴比值图像裁剪比例3斜向对称性旋转时插值误差4中心能量集中度高光、阴影5细长结构的轴向分布轮廓断裂6二阶方向与三阶方向的耦合非刚体形变7镜像区分可选噪声敏感第 7 矩对镜像敏感用在检索场景通常策略是去掉或只作辅助因为真实图片库里的“镜像同一物体”很少反而会放大噪声。后续比对检索时可先做个小实验加载 H.mat把第 7 列全部设 0看检索命中率是否下降。3. 特征库批量抽取与GUI界面数据流3.1 database 图库的构成与特征矩阵设计工程根目录下 database 文件夹存放了全部待检索图片文件命名能看出主题分布berlin (1).jpg到berlin (7).jpg是柏林城市系列asterix*.jpg是阿斯特里克斯漫画封面beer*.jpg构成啤酒瓶系列2008-subaru*.jpg、307wrc_front.jpg是赛车nirvana、pearl_jam是音乐专辑封面还有篮球、国旗、鳄鱼、伦敦芝加哥城市景观等类别。这种混合库对算法是种压力测试——漫画封面是纯色块加线条城市照片是密集纹理啤酒瓶是细长轮廓一个矩描述子要在这些差异巨大的分布上都有判别力。GetDatabaseVec.m 的职责是把整个 database 文件夹扫一遍对每个 jpg 调用 get_hu_vec 抽取 7 维 Hu 矩也可能叠加 get_color_vec 的颜色向量最后拼成一张 NumImages × FeatureDim 的特征矩阵。核心循环长这样files dir(fullfile(database, *.jpg)); numImgs length(files); featMat zeros(numImgs, 7); % 只存Hu矩7维 for i 1:numImgs img imread(fullfile(database, files(i).name)); featMat(i, :) get_hu_vec(img); % 如需多特征融合这里再把 get_color_vec(img) 横向拼接 end save(H.mat, featMat, files);dir的通配符*.jpg把 jpg 全部列出fullfile拼接路径避免手动字符串拼接漏掉分隔符。最后save到 H.mat 相当于一次性缓存特征库后续查询只需 load不用重新抽取。3.2 MainForm.fig 的界面布局与回调分工MainForm.fig 是 GUIDE 生成的界面文件MainForm.m 是它的回调函数集合。界面通常分两个区域左侧放查询图像一个 axes右侧以网格形式排布检索结果多个 axes 或一个 axes 分区重绘。控件至少包括“打开图像”“开始检索”“清空结果”三个按钮分别绑定 OpenImageFile.m 和 SearchResult.m。GUI 与检索链路之间的数据传递很关键。MainForm.m 的 OpeningFcn 里会预加载 H.mat 到 handles 结构体这样点检索时不需要再次读磁盘。常见做法是function MainForm_OpeningFcn(hObject, eventdata, handles, varargin) handles.features load(H.mat); % 特征库 handles.database handles.features.files; guidata(hObject, handles); % 回写handlesguidata是 GUI 世界里传递数据的标准接口所有回调函数都能通过handles访问特征库不用全局变量。新手最容易犯的错是把load写进按钮回调里每点一次读一次文件数据量大时卡顿明显。3.3 为什么不让你直接运行子函数文件运行说明里强调“不要直接运行子函数文件”原因在于 OpenImageFile.m、SearchResult.m、get_hu_vec.m 这些文件本质是函数没有独立输入参数直接按 F5 会报“Not enough input arguments”。即使某些函数设计了默认参数它们也不负责加载界面、初始化特征库跑起来什么都看不到。工程的实际唤醒点是 Runme.m脚本内容一般就是把 MainForm 推上屏幕% Runme.m —— 工程唯一入口 clear; clc; addpath(genpath(pwd)); % 把当前目录及子目录加入搜索路径 MainForm; % 启动GUIaddpath(genpath(pwd))把 database、子函数全部纳入 MATLAB 搜索路径MainForm 里的回调才能按函数名直接调用。这一步不做运行时会提示“未定义函数或变量”。之所以强调当前文件夹必须是工程路径是因为pwd取的是 MATLAB 左侧“当前文件夹”的值如果停在别的目录addpath 加错路径特征库、fig 文件全都找不到。4. 查询检索实现相似度度量与结果排序4.1 SearchResult.m 的“比对—排序—回填”三步用户选定查询图并点击检索后SearchResult.m 执行完整查询流程。先从文件读入查询图抽取与特征库同维的特征向量然后计算查询向量与 H.mat 中每一行的距离排序后取前 K 个最后把对应图片按顺序显示到界面上。核心代码片段function SearchResult(handles, queryImg) qFeat get_hu_vec(queryImg); % 1×7查询特征 feats handles.features.featMat; % N×7库特征 N size(feats, 1); dist zeros(N, 1); for i 1:N diff qFeat - feats(i, :); dist(i) sqrt(sum(diff.^2)); % 欧氏距离 end [~, idx] sort(dist, ascend); % 从小到大 showTopK(handles, idx(1:8), dist(idx(1:8))); endsqrt(sum(diff.^2))是标准 L2 距离sort升序排列后idx保留的是图片编号而不是距离值本身showTopK 负责把库中对应图片按照缩略图网格重绘到结果区并在每张图下方标注距离。距离越小代表相似度越高第一张通常就是查询图本身——如果连自己都排不进前 3特征抽取环节大概率出了偏差。4.2 距离度量选型欧氏、余弦还是曼哈顿下面这张对比表是基于我拿 H.mat 里这些图片跑过几十次查询后总结的倾向性结论。它不一定对每种图库都成立但至少能帮助理解为什么默认工程用欧氏距离以及什么情况下值得换。度量方式计算公式适用场景该工程里的表现欧氏距离L2(\sqrt{\sum (q-f)^2})特征维度量纲统一时最好用城市景观、漫画封面效果好曼哈顿距离L1(\sum |q-f|)特征含较多离群噪声对啤酒瓶这类轮廓图片略稳余弦相似度(\frac{q \cdot f}{|q||f|})关注方向而非幅值万一未做对数压缩余弦反而更抗量纲差这组对比说明如果跳过了 2.3 节的对数压缩余弦距离强于欧氏压缩做好后欧氏距离已经够用。不建议直接用马氏距离特征维数只有 7协方差矩阵估计不稳。4.3 一次实际查询beer 系列为什么容易互相窜拿beer.jpg作为查询图检索 Top-5结果里往往出现beer1.jpg、0412beer2.jpg这些同主题图但偶尔会混入a_clockwork_orange_04.263124651_std.jpg——《发条橙》海报。原因在于电影海报的背景也是暗色主体区域的灰度分布接近Hu 矩把两幅图在五六维上压得很近。这就是矩描述子的天花板它统计的是像素分布的形状不是语义。专辑封面类图片里nirvana和pearl_jam也容易出现这种串扰因为封面大多有大幅人脸特写灰度直方图形状相似。想验证是不是矩的锅可以把输出特征打印出来disp(get_hu_vec(imread(beer.jpg))); disp(get_hu_vec(imread(a_clockwork_orange_04.263124651_std.jpg)));对比第 3 维到第 7 维你会发现这些维度的相对趋势几乎一致。这是系统本身无法消除的误差要做的是在查询质量上下功夫优先查库里和类内差异较大的图比如城市全景避开纯色背景加单一大物体的图。5. 让检索更可靠统一入口、特征cache与Top-K验证技巧这套工程最实用的三个优化点都集中在“让复现不翻车”上。第一是用脚本固定路径。把 Runme.m 的开头从addpath(genpath(pwd))改成先切到工程绝对路径curPath fileparts(mfilename(fullpath)); cd(curPath); addpath(genpath(curPath));mfilename(fullpath)返回 Runme.m 的完整路径fileparts去掉文件名只留目录这样无论 MATLAB 当前文件夹停在哪儿双击 Runme.m 都能自动跳到工程根目录。这个技巧对任何 MATLAB 项目都通用也是解决“当前文件夹不在工程路径”报错的一劳永逸方案。第二是 H.mat 的特征缓存意识。首次运行 GetDatabaseVec.m 生成 H.mat 后再启动系统加载的是磁盘缓存。如果往 database 里新增了图片必须重新运行 GetDatabaseVec.m 刷新缓存否则查询时矩阵行数与files元胞数组对不上索引越界报错。判断缓存是否过期最简单的方法比较 H.mat 里的files长度和当前 database 目录下 jpg 数量不一致就重抽。第三是 Top-K 召回率验证。对一个小图库可以写一个自动评测脚本遍历库中每张图作为查询统计它在返回的前 K 个结果里是否包含自己所在类别的其他图片hitCount 0; totalQueries numel(files); for i 1:totalQueries q imread(fullfile(database, files(i).name)); [idx, ~] runSearch(q, H); % 返回排序索引 if any(ismember(idx(2:6), sameCategory(i))) hitCount hitCount 1; end end fprintf(Top-5命中率: %.2f%%\n, hitCount / totalQueries * 100);sameCategory需要人工定义类别归属城市系列、啤酒系列、漫画封面各算一组。这套脚本能把“感觉检索结果还行”变成量化的百分比课程设计报告里放上这个数字比放十张截图有说服力。如果命中率低于 60%优先检查第 2.3 节的对数压缩是否生效。本文还有配套的精品资源点击获取
返回列表