图像处理库 手机端可用

2020-05-04 15:46:16 qq_42662568 阅读数 1545
  • Linux系统目录及必备命令学习

    通过本课程的学习,大家可以从懵懂到熟悉,从熟悉到熟练,能够在企业中熟练运用,同时在学习的过程中有任何不明白的地方,都可以向我咨询,我会积极帮助大家解决问题。

    217人学习 吴光科
    免费试看

在这里插入图片描述


前前后后花了差不多两周的时间,终于完成了最后一个图像处理大作业,由于自己太菜,这个作业属实有点难顶哦,不过还好成功实现并按时提交,为自己干杯,哈哈!本篇博客记录自己的学习笔记及过程,以备以后回味和复习。

IDE:Jupyter notebook

题目:用手机或者相机拍摄不同角度图像(彼此之间有一定的重叠),用SIFT算子提取特征,通过特征匹配、图像旋转和图像融合等操作,将图像拼接在一起,形成大场景图像。

1. 实验思路

(1)尝试采用SIFT特征描述子提取特征;

(2)尝试特征匹配;

(3)找到变换矩阵,变换图像;

(4)拼接融合图像。

2. 实验原理

SIFT算法简介

SIFT (Scale-invariant feature transform)尺度不变特征转换即是一种计算机视觉的算法。它用来侦测与描述影像中的局部性特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变量,此算法由 David Lowe在1999年所发表,2004年完善总结。

SIFT算法的实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。

SIFT算法的特点有:

  1. SIFT特征是图像的局部特征,其对旋转、尺度缩放、亮度变化保持不变性,对视角变化、仿射变换、噪声也保持一定程度的稳定性;

  2. 独特性(Distinctiveness)好,信息量丰富,适用于在海量特征数据库中进行快速、准确的匹配;

  3. 多量性,即使少数的几个物体也可以产生大量的SIFT特征向量;

  4. 高速性,经优化的SIFT匹配算法甚至可以达到实时的要求;

  5. 可扩展性,可以很方便的与其他形式的特征向量进行联合。

算法流程

在这里插入图片描述

SIFT算法实现物体识别主要有三大工序,1、提取关键点;2、对关键点附加详细的信息(局部特征)也就是所谓的描述器;3、通过两方特征点(附带上特征向量的关键点)的两两比较找出相互匹配的若干对特征点,也就建立了景物间的对应关系。

SIFT算法操作步骤

1. 关键点检测

1.1 哪些是SIFT中要查找的关键点(特征点)?

所为关键点,就是在不同尺度空间的图像下检测出的具有方向信息的局部极值点。可以得出关键点具有的三个特征:尺度、方向、大小。

1.2 什么是尺度空间(scale space)?

尺度空间理论最早是在1962年提出,其主要思想是通过对原始图像进行尺度变换,获得图像多尺度下的尺度空间表示序列,对这些序列进行尺度空间主轮廓的提取,并以该主轮廓作为一种特征向量,实现边缘、角点检测和不同分辨率上的特征提取等。

尺度空间中各尺度图像的模糊程度逐渐变大,能够模拟人在距离目标由近到远时目标在视网膜上的形成过程。尺度越大,图像越模糊。

高斯核是唯一可以产生多尺度空间的核,一个图像的尺度空间,L(x,y,σ)L(x,y,\sigma),定义为原始图像I(x,y)I(x,y)与一个可变尺度的2维高斯函数G(x,y,σ)G(x,y,\sigma)卷积运算。高斯函数定义为:

G(xi,yi,σ)=12πσ2exp((xxi)2=(yyi)22σ2)G(x_i,y_i,\sigma)=\frac{1}{2\pi\sigma^2}exp(-\frac{(x-x_i)^2=(y-y_i)^2}{2\sigma^2})

L(x,y,σ)=G(x,y,σ)I(x,y)L(x,y,\sigma)=G(x,y,\sigma)*I(x,y)

尺度是自然存在的,不是人为创造的!高斯卷积只是表现尺度空间的一种形式……

1.3 高斯模糊

高斯模糊通常用来减小图像噪声以及降低细节层次,这种模糊技术生成的图像的视觉效果是好像经过一个半透明的屏幕观察图像。

G(r)=12πσ2exp(r22σ2)G(r)=\frac{1}{2\pi\sigma^2}exp(-\frac{r^2}{2\sigma^2})

rr为模糊半径,r=x2+y2r=\sqrt{x^2+y^2}

在实际应用中,在计算高斯函数的离散近似值时,在大概3σ3\sigma距离之外的像素都可以看作不起作用,这些像素的计算就可以忽略。

对一幅图像进行多次连续高斯模糊的效果与一次更大的高斯模糊可以产生同样的效果。例如,使用半径为6和8的两次高斯模糊变换得到的效果等同于一次半径为10的高斯模糊效果,62+82=10\sqrt{6^2+8^2}=10

1.4 高斯金字塔

高斯金字塔的构建过程可分为两步:(1)对图像做高斯平滑;(2)对图像做降采样。为了让尺度体现连续性,在简单下采样的基础上加上了高斯滤波。一幅图像可以产生几组(octave)图像,一组图像包括几层(interval)图像。

在这里插入图片描述

高斯金字塔共o组、s层,则有:σ(s)=σ02sS\sigma(s)=\sigma_02^\frac{s}{S}

σ\sigma——尺度空间坐标;s——sub-level层坐标;σ0\sigma_0——初始尺度;SS——每组层数(一般为3~5层)。

高斯金字塔的组内尺度与组间尺度:组内尺度是指同一组(octave)内的尺度关系,σs+1=σs21S\sigma_{s+1}=\sigma_s2^\frac{1}{S},组间尺度是指不同组直接的尺度关系,相邻组的尺度可化为:σo+1(s)=σo2s+SS\sigma_{o+1}(s)=\sigma_o2^\frac{s+S}{S}σo2s+SS=2σo2sS\sigma_o2^\frac{s+S}{S}=2\sigma_o2^\frac{s}{S}。由此可见,相邻两组的同一层尺度为2倍关系。

1.5 差分高斯金字塔

差分金字塔的是在高斯金字塔的基础上操作的,其建立过程是:在高斯金子塔中的每组中相邻两层相减(下一层减上一层)就生成高斯差分金字塔.高斯差分金字塔其操作如下图:

在这里插入图片描述

我们可以通过高斯差分金字塔图像看出图像上的像素值变化情况。(如果没有变化,也就没有特征。特征必须是变化尽可能多的点。)DOG图像描绘的是目标的轮廓。

在Lowe的论文中,将第0层的初始尺度定为1.6,图片的初始尺度定位0.5,则图像金字塔第0层的实际尺度为1.61.60.50.5=1.52\sqrt{1.6*1.6-0.5*0.5}=1.52,在检测极值点前对原始图像的高斯平滑以致图像丢失高频信息,所以Lowe建议在建立尺度空间前首先对原始图像长宽扩展一倍,以保留原始图像信息,增加特征点数量。当对图像长宽扩展一倍时,便构建了-1层,该层尺度为1.61.6(20.5)(20.5)=1.25\sqrt{1.6*1.6-(2*0.5)*(2*0.5)}=1.25

1.6 极值点检测

关键点是由DOG空间的局部极值点组成的,关键点的初步探查是通过同一组内各DOG相邻两层图像之间比较完成的。为了寻找DOG函数的极值点,每一个像素点要和它所有的相邻点比较,看其是否比它的图像域和尺度域的相邻点大或者小。如图下图所示,中间的检测点和它同尺度的8个相邻点和上下相邻尺度对应的9×2个点共26个点比较,以确保在尺度空间和二维图像空间都检测到极值点。

在这里插入图片描述

1.7 关键点精确定位

由于DOG值对噪声和边缘较敏感,因此,在上面DOG尺度空间中检测到局部极值点还要经过进一步的检验才能精确定位特征点。为了提高关键点的稳定性,需要对尺度空间DOG函数进行曲线拟合。利用DOG函数在尺度空间的Taylor展开式(插值函数)为:

任意一极值点在其 X0=(x0,y0,σ0)X 0=(x 0, \quad y 0, \quad \sigma 0) 处泰勒展开并舍掉 2 阶以后的项结果如下:f([xyσ])f([x0y0σ0])+[fxfyfσ]([xyσ][x0y0σ0])f\left(\left[\begin{array}{l}x \\ y \\ \sigma\end{array}\right]\right) \approx f\left(\left[\begin{array}{l}x_{0} \\ y_{0} \\ \sigma_{0}\end{array}\right]\right)+\left[\begin{array}{lll}\frac{\partial f}{\partial x} & \frac{\partial f}{\partial y} & \frac{\partial f}{\partial \sigma}\end{array}\right]\left(\left[\begin{array}{l}x \\ y \\ \sigma\end{array}\right]-\left[\begin{array}{l}x_{0} \\ y_{0} \\ \sigma_{0}\end{array}\right]\right)

12([xyσ][x0y0σ0])[2fxx2fxy2fxσ2fxy2fyy2πyσ2fxσ2fyσ2fσσ]([xyσ][x0y0σ0])\left.\frac{1}{2}\left([\begin{array}{ccccccc}x & y & \sigma\end{array}\right]-\left[\begin{array}{ccc}x_{0} & y_{0} & \sigma_{0}\end{array}\right]\right)\left[\begin{array}{ccc}\frac{\partial^{2} f}{\partial x \partial x} & \frac{\partial^{2} f}{\partial x \partial y} & \frac{\partial^{2} f}{\partial x \partial \sigma} \\ \frac{\partial^{2} f}{\partial x \partial y} & \frac{\partial^{2} f}{\partial y \partial y} & \frac{\partial^{2} \pi}{\partial y \partial \sigma} \\ \frac{\partial^{2} f}{\partial x \partial \sigma} & \frac{\partial^{2} f}{\partial y \partial \sigma} & \frac{\partial^{2} f}{\partial \sigma \partial \sigma}\end{array}\right]\left(\left[\begin{array}{l} x \\ y \\ \sigma \end{array}\right]-\left[\begin{array}{l} x_{0} \\ y_{0} \\ \sigma_{0} \end{array}\right]\right)

其中 f 的一阶偏导数,二阶偏导数,以及二阶混合偏导数由下面几个公式求(h=1) 得:

fx=f(i,j+1)f(i,j1)2h,fy=f(i+1,j)f(i1,j)2h\frac{\partial f}{\partial x}=\frac{f(i, j+1)-f(i, j-1)}{2 h}, \quad \frac{\partial f}{\partial y}=\frac{f(i+1, j)-f(i-1, j)}{2 h}

2fx2=f(i,j+1)+f(i,j1)2f(i,j)h2,2fy2=f(i+1,j)+f(i1,j)2jh2\frac{\partial^{2} f}{\partial x^{2}}=\frac{f(i, j+1)+f(i, j-1)-2 f(i, j)}{h^{2}}, \quad \frac{\partial^{2} f}{\partial y^{2}}=\frac{f(i+1, j)+f(i-1, j)-2 j}{h^{2}}

2fxy=f(i1,j1)+f(i+1,j+1)f(i1,j+1)f(i+1,j1)4h2\frac{\partial^{2} f}{\partial x \partial y}=\frac{f(i-1, j-1)+f(i+1, j+1)-f(i-1, j+1)-f(i+1, j-1)}{4 h^{2}}

上面算式的矩阵表示如下:

D(X)=D+DTXX+12XT2DX2XD(X)=D+\frac{\partial D^{T}}{\partial X} X+\frac{1}{2} X^{T} \frac{\partial^{2} D}{\partial X^{2}} X,其中,X求导并让方程等于0,可得极值点的偏移量为X^=2D1X2DX\hat{X}=-\frac{\partial^{2} D^{-1}}{\partial X^{2}} \frac{\partial D}{\partial X},对应极值点,方程的值为D(X^)=D+12DTXX^D(\hat{X})=D+\frac{1}{2} \frac{\partial D^{T}}{\partial X} \hat{X}

其中, X^\hat{X}代表相对插值中心的偏移量, 当它在任 一维度上的偏移量大于0.5时 (即xxyyσ\sigma),意味着插值中心已经偏移到它的邻近点上, 所以必须改变当前关键点的位置。同时在新的位置上反复插值直到收敛;也有可能超出所设定的迭代次数或者超出图像边界的范围, 此时这样的点应该删除, 在Lowe中进行了5次迭代。另外, 过小的点易受噪声的于扰而变得不稳定, 所以将 小于某个经验值(Lowe论文中使用0.030.03,Rob Hess等人实现时使用0.04/S0.04/S)的极值点删除。同时, 在此过程中获取特征点的精确位置(原位置加上拟合的偏移量以及尺度(σ\sigma)。

2. 关键点描述

2.1 关键点方向匹配

为了使描述符具有旋转不变性,需要利用图像的局部特征为给每一个关键点分配一个基准方向。使用图像梯度的方法求取局部结构的稳定方向。

(1)梯度计算

对于在DOG金字塔中检测出的关键点,采集其所在高斯金字塔图像3σ领域窗口内像素的梯度和方向分布特征。梯度的模值和方向如下:

在这里插入图片描述

(2)梯度直方图

  1. 直方图以每10度方向为一个柱,共36个柱,柱所代表的的方向为像素点梯度方向,柱的长短代表了梯度幅值。
  2. 根据Lowe的建议,直方图1统计半径采用31.5σ3*1.5*\sigma
  3. 在直方图统计时每相邻三个像素点采用高斯加权,模板采用[0.25,0.5,0.25][0.25,0.5,0.25],并连续加权两次。

在这里插入图片描述

(3)特征点主方向的确定

方向直方图的峰值则代表了该特征点处邻域梯度的方向,以直方图中最大值作为该关键点的主方向。为了增强匹配的鲁棒性,只保留峰值大于主方向峰值80%的方向作为该关键点的辅方向。因此,对于同一梯度值的多个峰值的关键点位置,在相同位置和尺度将会有多个关键点被创建但方向不同。仅有15%的关键点被赋予多个方向,但可以明显的提高关键点匹配的稳定性。实际编程实现中,就是把该关键点复制成多份关键点,并将方向值分别赋给这些复制后的关键点,并且,离散的梯度方向直方图要进行插值拟合处理,来求得更精确的方向角度值。

为了防止某个梯度方向角度因受到噪声的干扰而突变,我们还需要对梯度方向直方图进行平滑处理,平滑公式为:

H(i)=h(i2)+h(i+2)16+4×(h(i1)+h(i+1))16+6×h(i)16H(i)=\frac{h(i-2)+h(i+2)}{16}+\frac{4 \times(h(i-1)+h(i+1))}{16}+\frac{6 \times h(i)}{16}

其中i∈[0,35],hhHH分别表示平滑前和平滑后的直方图。由于角度是循环的,即0=3600^{\circ}=360^{\circ},如果出现h(j)h(j)j超出了(0,…,35)的范围,那么可以通过圆周循环的方法找到它所对应的、在0=3600^{\circ}=360^{\circ}之间的值,如h(-1) = h(35)。

(4)梯度直方图抛物线插值

在这里插入图片描述

假设我们在第i个小柱子要找一个精确的方向,那么由上面分析知道:设插值抛物线方程为h(t)=at2bt+ch(t)=at^2-bt+c,其中abca、b、c为执物线的系数,tt自变量, t[1,1]t\in[-1,1],此抛物线求导并令它等于0。
h(t)=0h(t)'=0tmax=b/(2a)t_max=-b/(2a)。现在把这三个插值点代入方程可得:

h(1)=ab+ch(0)=ch(1)=a+b+c}\left.\begin{array}{l}\mathrm{h}(-1)=\mathrm{a}-\mathrm{b}+\mathrm{c} \\ \mathrm{h}(0)=\mathrm{c} \\ \mathrm{h}(1)=\mathrm{a}+\mathrm{b}+\mathrm{c}\end{array}\right\}——>{a=[h(1)+h(1)]/2h(0)b=[h(1)h(1)]/2c=h(0)\left\{\begin{array}{l}-\mathrm{a}=[\mathrm{h}(1)+\mathrm{h}(-1)] / 2-\mathrm{h}(0) \\ \mathrm{b}=[\mathrm{h}(1)-\mathrm{h}(-1)] / 2 \\ \mathrm{c}=\mathrm{h}(0)\end{array}\right.

由上式知:tmax=b/(2a)=h(1)h(1)2[h(1)+h(1)2h(0)]\mathrm{t}_{\mathrm{max}}=-\mathrm{b} /(2 \mathrm{a})=\frac{h(-1)-h(1)}{2[h(-1)+h(1)-2 h(0)]}(局部坐标系中的取值)

i=i+h(i1)h(i+1)2[h(i1)+h(i+1)2h(i)]\mathbf{i}^{\prime}=\mathbf{i}+\frac{h(i-1)-h(i+1)}{2[h(i-1)+h(i+1)-2 h(i)]}(小柱子在直方图中的索引号)。

图像的关键点已检测完毕,每个关键点有三个信息:位置、尺度、方向;同时也就使关键点具备平移、缩放、旋转不变性。

2.2 生成描述符

(1)确定计算描述子所需的区域

描述子梯度方向直方图由关键点所在尺度的模糊图像计算产生。图像区域的半径通过下式计算:

radius=3σoct×2×(d+1)+12=\frac{3 \sigma_{\text {oct}} \times \sqrt{2} \times(d+1)+1}{2}σoct\sigma_{oct}是关键点所在组(octave)的组内尺度,d=4d=4

在这里插入图片描述

(2)将坐标移至关键点主方向

在这里插入图片描述

旋转角度后新坐标:(x^y^)=(cosθsinθsinθcosθ)×(xy)\left(\begin{array}{c}\hat{x} \\ \hat{y}\end{array}\right)=\left(\begin{array}{cc}\cos \theta & -\sin \theta \\ \sin \theta & \cos \theta\end{array}\right) \times\left(\begin{array}{l}x \\ y\end{array}\right)

(3)梯度直方图的生成

在窗口宽度为2X2的区域内计算8个方向的梯度方向直方图,绘制每个梯度方向的累加值,即可形成一个种子点。然后再在下一个2X2的区域内进行直方图统计,形成下一个种子点,共生成16个种子点。

在这里插入图片描述

(4)三线性插值

插值计算每个种子点八个方向的梯度。

在这里插入图片描述

采样点在子区域中的下标(x,y)(x'',y'')(图中蓝色窗口内红色点)线性插值,计算其对每个种子点的贡献。如图中的红色点,落在第0行和第1行之间,对这两行都有贡献。对第0行第3列种子点的贡献因子为drdr,对第1行第3列的贡献因子为1dr1-dr,同理,对邻近两列的贡献因子为dcdc1dc1-dc,对邻近两个方向的贡献因子为dodo1do1-do。则最终累加在每个方向上的梯度大小为:weight=wdrk(1dr)1kdcm(1dc)1mdon(1dO)1nweight=w*d r^{k}*(1-d r)^{1-k} * d c^{m}*(1-d c)^{1-m} * d o^{n} *(1-d O)^{1-n}。其中k,m,n为0(像素点超出了对要插值区间的四个邻近子区间所在范围)或为1(像素点处在对要插值区间的四个邻近子区间之一所在范围)。

(5)描述子生成过程

在这里插入图片描述

2.3 关键点匹配

分别对模板图和实时图建立关键点描述子集合。目标的识别是通过两点集内关键点描述子的对比来完成。具有128维的关键点描述子的相似性度量采样欧氏距离。

模板图中关键点描述子:Ri=(ri1,ri2,,ri128)R_{i}=\left(r_{i 1}, r_{i 2}, \cdots, r_{i 128}\right)

实时图中关键点描述子:Si=(si1,si2,,si128)S_{i}=\left(s_{i 1}, s_{i 2}, \cdots, s_{i 128}\right)

任意两描述子相似性度量:d(Ri,Si)=j=1128(rijsij)2d(R_i,S_i)=\sqrt{\sum\limits_{j=1}^{128}(r_{ij}-s_{ij})^2}

要得到配对的关键点描述子需满足:RiSjRiSp<Threshold\frac{实时图中距离R_i最近的点S_j}{实时图中距离R_i的次最近点S_p}<Threshold

单应矩阵(Homography)

有了两组相关点,接下来就需要建立两组点的转换关系,也就是图像变换关系。单应性是两个空间之间的映射,常用于表示同一场景的两个图像之间的对应关系,可以匹配大部分相关的特征点,并且能实现图像投影,使一张图通过投影和另一张图实现大面积的重合。

用RANSAC方法估算H:

  1. 首先检测两边图像的角点
  2. 在角点之间应用方差归一化相关,收集相关性足够高的对,形成一组候选匹配。
  3. 选择四个点,计算H
  4. 选择与单应性一致的配对。如果对于某些阈值:Dist(Hp、q) <ε,则点对(p, q)被认为与单应性H一致
  5. 重复34步,直到足够多的点对满足H
  6. 使用所有满足条件的点对,通过公式重新计算H

RANSAC(Random Sample Consensus,随机抽样一致)是一种鲁棒性的参数估计方法。它的实质就是一个反复测试、不断迭代的过程。

基本思想:首先根据具体问题设计出某个目标函数,然后通过反复提取最小点集估计该函数中参数的初始值,利用这些初始值把所有的数据分为“内点”和“外点”,最后用所有的内点重新计算和估计函数的参数。

在这里插入图片描述

图像变形和融合

(1)图像变形

  1. 首先计算每个输入图像的变形图像坐标范围,得到输出图像大小,可以很容易地通过映射每个源图像的四个角并且计算坐标(x,y)的最小值和最大值确定输出图像的大小。最后,需要计算指定参考图像原点相对于输出全景图的偏移量的偏移量x_offset和偏移量y_offset。
  2. 下一步是使用上面所述的反向变形,将每个输入图像的像素映射到参考图像定义的平面上,分别执行点的正向变形和反向变形。

(2)图像融合

最后一步是在重叠区域融合像素颜色,以避免接缝。最简单的可用形式是使用羽化(feathering),它使用加权平均颜色值融合重叠的像素。我们通常使用alpha因子,通常称为alpha通道,它在中心像素处的值为1,在与边界像素线性递减后变为0。当输出拼接图像中至少有两幅重叠图像时,我们将使用如下的alpha值来计算其中一个像素处的颜色:假设两个图像I1,I2I_1,I_2在输出图像中重叠;每个像素点(x,y)(x,y)在图像Ii(x,y)=(αiR,αiG,αiB,αj)I_i(x,y)=(\alpha_iR,\alpha_iG,\alpha_iB,\alpha_j),其中(R,G,B)(R,G,B)是每个通道像素值,我们将在缝合后的输出图像中计算(x,y)(x,y)的像素值:

[(α1R,α1G,α1B,α1)+(α2R,α2G,α2B,α2)]/(α1+α2)[(\alpha_1R,\alpha_1G,\alpha_1B,\alpha_1)+(\alpha_2R,\alpha_2G,\alpha_2B,\alpha_2)]/(\alpha_1+\alpha_2)

3 代码实现

#################
#Author:Tian YJ#
#图像拼接实现全景图#
#################

# 导入基本库文件
import numpy as np 
from numpy import *
from numpy.linalg import det, lstsq, norm # 线性代数模块
import cv2
import matplotlib.pyplot as plt
from functools import cmp_to_key # 接受两个参数,将两个参数做处理

# 加上这两行可以一次性输出多个变量而不用print
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
# 设置容忍度
float_tolerance = 1e-7

%matplotlib inline 
##################
#设置路径、读取图片#
##################
# 设置路径
path = 'C:\\Users\\86187\\Desktop\\image\\'
# 读取待拼接图片(灰度图)
# img1为左图,img2位右图
img1 = cv2.imread(path + 'left.jpg', 0)
img2 = cv2.imread(path + 'right.jpg', 0)

# 原始图片展示
plt.figure(figsize=(25,10)) 
plt.subplot(1,2,1)
plt.imshow(img1.astype(np.uint8), cmap="gray")
plt.subplot(1,2,2)
plt.imshow(img2.astype(np.uint8), cmap="gray")
plt.show()

在这里插入图片描述

#############################
#对图像进行倍数放大(双线性插值)#
#############################
def resize(img, ratio=2.):
    """
    img: 待处理图片
    ratio: 放大倍数
    """
    # 目标图像尺寸
    new_shape = [int(img.shape[0] * ratio), int(img.shape[1] * ratio)]
    result = np.zeros((new_shape))  # 目标图像初始化
    # 遍历新的图像坐标
    for h in range(new_shape[0]):
        for w in range(new_shape[1]):
            # 对应的原图像上的点(向下取整,也就是左上点的位置)
            h0 = int(np.floor(h / ratio))
            w0 = int(np.floor(w / ratio))
            # 新图像的坐标/放缩比例 - 原图像坐标点 = 距离
            dx = h / ratio - h0
            dy = w / ratio - w0
            # 防止溢出
            h1 = h0 + 1 if h0 < img.shape[0] - 1 else h0
            w1 = w0 + 1 if w0 < img.shape[1] - 1 else w0
            # 进行插值计算
            result[h, w] = (1 - dx) * (1 - dy) * img[h0, w0] + dx * (
                1 - dy) * img[h1, w0] + (
                    1 - dx) * dy * img[h0, w1] + dx * dy * img[h1, w1]
    result = result.astype(np.uint8)
    return result
##################
#对图像进行边缘填充#
##################
def padding(img):
    """
    img: 待处理图片
    """
   
    # 获取图片尺寸
    H, W = img.shape
    pad = 2 # 填充尺寸

    # 先填充行
    rows = np.zeros((pad, W), dtype=np.uint8)
    # 再填充列
    cols = np.zeros((H + 2 * pad, pad), dtype=np.uint8)
    # 进行拼接
    img = np.vstack((rows, img, rows))  # 上下拼接
    img = np.hstack((cols, img, cols))  # 左右拼接

    # 进行镜像padding,我第一次padding零,出现黑边,边缘失真严重
    # 第一步,上下边框对称取值
    img[0, :] = img[2, :]
    img[-1, :] = img[-3, :]
    # 第二步,左右边框对称取值
    img[:, 0] = img[:, 2]
    img[:, -1] = img[:, -3]
    # 第三步,四个顶点对称
    img[0, 0] = img[0, 2]
    img[-1, 0] = img[-1, 2]
    img[0, -1] = img[0, -3]
    img[-1, -1] = img[-1, -3]

    return img
##############
#设置滤波器系数#
##############
def Kernel(K_sigma, K_size):
    """
    K_sigma: 模糊度
    K_size: 滤波器即卷积核尺寸
    """
   
    # 对滤波器进行初始化0
    pad = K_size // 2
    K = np.zeros((K_size, K_size), dtype=np.float)

    # 代入公式求高斯滤波器系数,并填入矩阵
    for x in range(-pad, -pad + K_size):
        for y in range(-pad, -pad + K_size):
            K[y + pad, x + pad] = np.exp(-(x**2 + y**2) / (2 * (K_sigma**2)))

    K /= K.sum()  # 进行归一化

    return K
#############
#进行高斯滤波#
#############
def gaussFilter(img, K_size=5, K_sigma=1.6):
    """
    img: 需要处理图像
    K_size: 滤波器尺寸
    K_sigma: 模糊度
    """

    # 获取图片尺寸
    pad = K_size // 2
    H, W = img.shape

    ## 对图片进行padding
    img = padding(img)

    # 滤波器矩阵
    K = Kernel(K_sigma, K_size)

    ## 进行滤波
    out = img.copy()
    for h in range(H):
        for w in range(W):
            out[pad + h, pad + w] = np.sum(K * out[h:h + K_size, w:w + K_size])
    # 截取像素合理值
    out = out / out.max() * 255
    out = out[pad:pad + H, pad:pad + W].astype(np.uint8)
    return out
##################
#生成金字塔基础图像#
##################
def generateBaseImage(image, sigma, assumed_blur):
    """
    将输入图像放大一倍并应用高斯模糊,以生成图像金字塔的基础图像
    image: 待处理图片
    sigma: 目标模糊度
    assumed_blur: 假设模糊度
    """
    # 进行2倍放大
    image = resize(image, ratio=2.0)
    # 对图像应用多个连续的高斯模糊效果与应用单个较大的高斯模糊效果相同
    sigma_diff = np.sqrt(max((sigma**2) - ((2 * assumed_blur)**2), 0.01))

    return gaussFilter(image, K_size=5, K_sigma=sigma_diff)
# 尝试一下
base_image = generateBaseImage(img1, 1.6, 0.5)
# cv2.imshow('result', base_image)
# cv2.imshow('begin', img1)
# cv2.waitKey(0)
####################################
#计算可以将图像重复减半直到变得很小的次数#
####################################
def computeNumberOfOctaves(image_shape):
    """
    image_shape: 图像尺寸
    """
    return int(round(np.log(min(image_shape)) / np.log(2) - 1))
####################################
#为特定图层中的每个图像创建一个模糊度列表#
####################################
def generateGaussianKernels(sigma, num_intervals):
    """
    sigma: 模糊度
    num_intervals: 能进行极值点检测的图层数
    高斯金字塔每组有num_intervals+1+2层
    """
    # 高斯金字塔每组层数
    num_images_per_octave = num_intervals + 3
    # 高斯模糊度系数
    k = 2**(1. / num_intervals)
    # 高斯模糊度列表初始化为0
    gaussian_kernels = np.zeros(num_images_per_octave)
    # 第一个高斯模糊度
    gaussian_kernels[0] = sigma

    # 第0层在升采样时已进行高斯模糊,故从第1层开始
    for image_index in range(1, num_images_per_octave):
        sigma_previous = (k**(image_index - 1)) * sigma
        sigma_total = k * sigma_previous
        gaussian_kernels[image_index] = np.sqrt(sigma_total**2 -
                                             sigma_previous**2)
    return gaussian_kernels
#####################
#生成尺度空间高斯金字塔#
#####################
def generateGaussianImages(image, num_octaves, gaussian_kernels):
    """
    image: 输入基图像
    num_octaves: 尺度金字塔组数
    gaussian_kernels: 每一组的高斯模糊度列表
    """
    # 总的高斯金字塔列表
    gaussian_images = []

    for octave_index in range(num_octaves):
        # 每一组的高斯金字塔列表
        gaussian_images_in_octave = []
        gaussian_images_in_octave.append(image)  # 第一个图像已经滤波
        for gaussian_kernel in gaussian_kernels[1:]:
            # 进行高斯滤波
            image = gaussFilter(image, K_size=5, K_sigma=gaussian_kernel)
            gaussian_images_in_octave.append(image)
        gaussian_images.append(gaussian_images_in_octave)
        # 将上一组的倒数第三层作为下一组的基图像
        octave_base = gaussian_images_in_octave[-3]  # 倒数第三层
        image = octave_base[::2, ::2]  # 下采样
    return array(gaussian_images)
# 打印高斯模糊度列表
gaussian_kernels = generateGaussianKernels(1.6, 3)
print(gaussian_kernels)
# 显示高斯金字塔图像
gaussian_images = generateGaussianImages(base_image, 8, gaussian_kernels)

for k in range(len(gaussian_images)):
    plt.figure(figsize=(25, 10))
    for i in range(len(gaussian_images[k])):
        plt.subplot(1, len(gaussian_images[k]), i + 1)
        plt.imshow(gaussian_images[k][i].astype(np.uint8), cmap="gray")
plt.show()

[1.6 1.2262735 1.54500779 1.94658784 2.452547 3.09001559]
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#################
#生成高斯差分金字塔#
#################
def generateDoGImages(gaussian_images):
    """
    gaussian_images: 传入高斯金字塔组
    """
    # 总的差分金字塔列表
    dog_images = []

    for gaussian_images_in_octave in gaussian_images:
        # 每一组高斯差分金字塔列表
        dog_images_in_octave = []
        # 两两进行差分运算
        for first_image, second_image in zip(gaussian_images_in_octave,
                                             gaussian_images_in_octave[1:]):
            dog_images_in_octave.append(cv2.subtract(
                second_image, first_image))  # 普通的减法不行,因为图像是无符号整数
        dog_images.append(dog_images_in_octave)
    return array(dog_images)
# 显示差分金字塔图像
dog_images = generateDoGImages(gaussian_images)
for k in range(len(dog_images)):
    plt.figure(figsize=(25, 10))
    for i in range(len(dog_images[k])):
        plt.subplot(1, len(dog_images[k]), i + 1)
        plt.imshow(dog_images[k][i].astype(np.uint8), cmap="gray")
plt.show()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

####################
#查找极值点的像素位置#
####################
def findScaleSpaceExtrema(gaussian_images,
                          dog_images,
                          num_intervals,
                          sigma,
                          image_border_width,
                          contrast_threshold=0.04):
    """
    gaussian_images: 高斯金字塔组
    dog_images: 差分金字塔组
    num_intervals:每一组极值点检测层数
    sigma:模糊度
    image_border_width:靠近图像边缘5个像素的区域不做检测
    contrast_threshold:对比度阈值
    """
    # 阈值化,不保留低于阈值的不稳定点
    # abs(val)  > 0.5*T/n
    threshold = np.floor(0.5 * contrast_threshold / num_intervals * 255)
    # 关键点列表
    keypoints = []
    # 遍历DoG金字塔
    for octave_index, dog_images_in_octave in enumerate(dog_images):
        # dog_images_in_octave是一个列表,每一个包含5张图片
        # dog_images_in_octave[1:],包含4张图片
        # dog_images_in_octave[2:],包含3张图片
        for image_index, (first_image, second_image, third_image) in enumerate(
                zip(dog_images_in_octave, dog_images_in_octave[1:],
                    dog_images_in_octave[2:])):
            # 这里(0,1,2)、(1,2,3)、(2,3,4) 每3张图片分别是一组
            # (i, j) 是3x3矩阵的中心
            # 靠近图像边缘5个像素的区域不做检测,image_border_width=5
            for i in range(image_border_width,
                           first_image.shape[0] - image_border_width):
                for j in range(image_border_width,
                               first_image.shape[1] - image_border_width):
                    ## 调用函数判别极值
                    if isPixelAnExtremum(
                            first_image[i - 1:i + 2, j - 1:j + 2],
                            second_image[i - 1:i + 2, j - 1:j + 2],
                            third_image[i - 1:i + 2, j - 1:j + 2], threshold):
                        ## 调用函数定位极值点(精确定位)
                        localization_result = localizeExtremumViaQuadraticFit(
                            i, j, image_index + 1, octave_index, num_intervals,
                            dog_images_in_octave, sigma, contrast_threshold,
                            image_border_width)
                        if localization_result is not None:
                            keypoint, localized_image_index = localization_result
                            # 计算关键点方向
                            keypoints_with_orientations = computeKeypointsWithOrientations(
                                keypoint, octave_index,
                                gaussian_images[octave_index]
                                [localized_image_index])
                            for keypoint_with_orientation in keypoints_with_orientations:
                                keypoints.append(keypoint_with_orientation)
    return keypoints
#############
#进行极值判别#
#############
def isPixelAnExtremum(first_subimage, second_subimage, third_subimage,
                      threshold):
    """
    first_subimage:第一张图片
    second_subimage:第二张图片
    third_subimage:第三张图片
    threshold:阈值
    满足条件返回True,不满足条件返回False
    """
    center_pixel_value = second_subimage[1, 1]  # 中心像素为第二层中间者
    # 小于阈值的极值点删除
    if abs(center_pixel_value) > threshold:
        if center_pixel_value > 0:
            # 正值情况
            # 分别与上一层9个、下一层9个和本层8个像素进行比较
            return all(center_pixel_value >= first_subimage) and \
                   all(center_pixel_value >= third_subimage) and \
                   all(center_pixel_value >= second_subimage[0, :]) and \
                   all(center_pixel_value >= second_subimage[2, :]) and \
                   center_pixel_value >= second_subimage[1, 0] and \
                   center_pixel_value >= second_subimage[1, 2]
        elif center_pixel_value < 0:
            # 负值情况
            # 分别于上一层9个、一层9个和本层8个像素进行比较
            return all(center_pixel_value <= first_subimage) and \
                   all(center_pixel_value <= third_subimage) and \
                   all(center_pixel_value <= second_subimage[0, :]) and \
                   all(center_pixel_value <= second_subimage[2, :]) and \
                   center_pixel_value <= second_subimage[1, 0] and \
                   center_pixel_value <= second_subimage[1, 2]
    return False
#####################
#二次拟合精确定位极值点#
#####################
def localizeExtremumViaQuadraticFit(i,
                                    j,
                                    image_index,
                                    octave_index,
                                    num_intervals,
                                    dog_images_in_octave,
                                    sigma,
                                    contrast_threshold,
                                    image_border_width,
                                    eigenvalue_ratio=10,
                                    num_attempts_until_convergence=5):
    """
    i,j:中心像素点原坐标
    image_index:每一octave种的图像索引
    octave_index:差分金字塔octave索引
    num_intervals:每一组极值点检测层数
    dog_images_in_octave:高斯差分金字塔组,每一组4张图片
    sigma:高斯模糊度
    contrast_threshold:对比度阈值
    image_border_width:图像边界5像素不检测
    eigenvalue_ratio:主曲率阈值
    num_attempts_until_convergence:最大尝试次数
    """
    extremum_is_outside_image = False
    # 获取每一octave第一层图像尺寸
    image_shape = dog_images_in_octave[0].shape
    # 最大尝试次数设为5
    for attempt_index in range(num_attempts_until_convergence):
        first_image, second_image, third_image = dog_images_in_octave[
            image_index - 1:image_index + 2]
        # 纵向拼接形成三维数组
        pixel_cube = np.stack([
            first_image[i - 1:i + 2, j - 1:j + 2],
            second_image[i - 1:i + 2, j - 1:j + 2], third_image[i - 1:i + 2,
                                                                j - 1:j + 2]
        ]).astype('float32') / 255.
        # 需要从uint8转换为float32以计算导数,并且需要将像素值重新缩放为[0,1]以应用阈值

        # 计算梯度
        gradient = computeGradientAtCenterPixel(pixel_cube)
        # 计算海森阵
        hessian = computeHessianAtCenterPixel(pixel_cube)
        # 最小二乘拟合
        extremum_update = -lstsq(hessian, gradient, rcond=None)[0]
        # 如果当前偏移量绝对值中的每个值均小于0.5,放弃迭代
        if abs(extremum_update[0]) < 0.5 and abs(
                extremum_update[1]) < 0.5 and abs(extremum_update[2]) < 0.5:
            break
        # 更新中心点坐标,即极值点重定位
        j += int(round(extremum_update[0]))
        i += int(round(extremum_update[1]))
        image_index += int(round(extremum_update[2]))
        # 确保新的pixel_cube将完全位于图像中
        if i < image_border_width or i >= image_shape[
                0] - image_border_width or j < image_border_width or j >= image_shape[
                    1] - image_border_width or image_index < 1 or image_index > num_intervals:
            extremum_is_outside_image = True
            break
    if extremum_is_outside_image:
        # 更新的极值在达到收敛之前移出图像
        return None
    if attempt_index >= num_attempts_until_convergence - 1:
        # 超过最大尝试次数,但未达到此极值的收敛。
        return None
    functionValueAtUpdatedExtremum = pixel_cube[1, 1, 1] + 0.5 * np.dot(
        gradient, extremum_update)
    if abs(functionValueAtUpdatedExtremum
           ) * num_intervals >= contrast_threshold:
        xy_hessian = hessian[:2, :2]
        # trace求取xy_hessian的对角元素和
        xy_hessian_trace = trace(xy_hessian)
        # det为求xy_hessian的行列式值
        xy_hessian_det = det(xy_hessian)
        # 检测主曲率是否在域值eigenvalue_ratio下
        if xy_hessian_det > 0 and eigenvalue_ratio * (xy_hessian_trace**2) < (
            (eigenvalue_ratio + 1)**2) * xy_hessian_det:
            # 返回KeyPoint对象,
            keypoint = cv2.KeyPoint()
            # 关键点的点坐标
            keypoint.pt = ((j + extremum_update[0]) * (2**octave_index),
                           (i + extremum_update[1]) * (2**octave_index))
            # 从哪一层金字塔得到的此关键点
            keypoint.octave = octave_index + image_index * (2**8) + int(
                round((extremum_update[2] + 0.5) * 255)) * (2**16)
            # 关键点邻域直径大小
            keypoint.size = sigma * (2**(
                (image_index + extremum_update[2]) / np.float32(num_intervals)
            )) * (2**(octave_index + 1))  # octave_index + 1,因为输入的图像是原来的两倍
            # 响应程度,代表该点的强壮程度,也就是该点角点程度
            keypoint.response = abs(functionValueAtUpdatedExtremum)
            return keypoint, image_index
    return None
##############
#近似求离散梯度#
##############
def computeGradientAtCenterPixel(pixel_array):
    """
    pixel_array:3层3x3的像素区域,进行极值比较
    """
    # 对于步长h,f'(x)的中心差分公式为(f(x + h)-f(x-h))/(2 * h)
    # 此处h = 1,因此公式简化为f'(x)=(f(x + 1)-f(x-1))/ 2

    # x对应于第二个数组轴,y对应于第一个数组轴,s(尺度)对应于第三个数组轴

    dx = 0.5 * (pixel_array[1, 1, 2] - pixel_array[1, 1, 0])
    dy = 0.5 * (pixel_array[1, 2, 1] - pixel_array[1, 0, 1])
    ds = 0.5 * (pixel_array[2, 1, 1] - pixel_array[0, 1, 1])  # 跨层差分
    return np.array([dx, dy, ds])
#############
#近似求海森阵#
#############
def computeHessianAtCenterPixel(pixel_array):
    """
    """
    # 步长为h时,f"(x)的中心差分公式为(f(x+h)-2*f(x)+f(x-h))/(h^2)
    # 这里h= 1,公式化简为f"(x)=f(x+1)-2*f(x)+f(x-1)
    
    # 步长为h时,(d^2)f(x,y)/(dxdy)的中心差分公式为:
    # (f(x+h,y+h)-f(x+h,y-h)-f(x-h,y+h)+ f(x-h,y-h))/(4*h^2)
    # 在这里h = 1,因此公式简化为:
    # (d^2)f(x,y)/(dx dy)=(f(x+1,y+1)-f(x+1,y-1)-f(x-1,y+1)+f(x-1,y-1))/4
    
    # x对应于第二个数组轴,y对应于第一个数组轴,s(尺度)对应于第三个数组轴
    center_pixel_value = pixel_array[1, 1, 1] # 中心像素值
    dxx = pixel_array[1, 1, 2] - 2 * center_pixel_value + pixel_array[1, 1, 0]
    dyy = pixel_array[1, 2, 1] - 2 * center_pixel_value + pixel_array[1, 0, 1]
    dss = pixel_array[2, 1, 1] - 2 * center_pixel_value + pixel_array[0, 1, 1]
    dxy = 0.25 * (pixel_array[1, 2, 2] - pixel_array[1, 2, 0] -
                  pixel_array[1, 0, 2] + pixel_array[1, 0, 0])
    dxs = 0.25 * (pixel_array[2, 1, 2] - pixel_array[2, 1, 0] -
                  pixel_array[0, 1, 2] + pixel_array[0, 1, 0])
    dys = 0.25 * (pixel_array[2, 2, 1] - pixel_array[2, 0, 1] -
                  pixel_array[0, 2, 1] + pixel_array[0, 0, 1])
    return np.array([[dxx, dxy, dxs], [dxy, dyy, dys], [dxs, dys, dss]])
###############################
##########计算关键点方向#########
#为关键点附近的像素创建渐变的直方图#
###############################
def computeKeypointsWithOrientations(keypoint,
                                     octave_index,
                                     gaussian_image,
                                     radius_factor=3,
                                     num_bins=36,
                                     peak_ratio=0.8,
                                     scale_factor=1.5):
    """
    keypoint:检测到精确并定位的关键点
    octave_index:差分金字塔octave索引
    gaussian_image:高斯金字塔组
    radius_factor:半径因子
    num_bins:直方图柱数,没0度一柱
    peak_ratio:只保留峰值大于主方向峰值80%的方向作为该关键点的辅方向
    scale_factor:尺度因子
    """
    keypoints_with_orientations = []
    image_shape = gaussian_image.shape

    # scale = 1.5*sigma
    scale = scale_factor * keypoint.size / np.float32(2**(octave_index + 1))
    # 直方图统计半径采用3*1.5*sigma
    radius = int(round(radius_factor * scale))
    # 权重因子
    weight_factor = -0.5 / (scale**2)
    # 梯度直方图将0~360度的方向范围分为36个柱(bins),其中每柱10度
    # num_bins=36
    raw_histogram = np.zeros(num_bins)
    # 高斯平滑直方图
    smooth_histogram = np.zeros(num_bins)

    # 采集其所在高斯金字塔图像3σ领域窗口内像素的梯度和方向分布特征
    for i in range(-radius, radius + 1):
        region_y = int(round(keypoint.pt[1] / np.float32(2**octave_index))) + i
        if region_y > 0 and region_y < image_shape[0] - 1:
            for j in range(-radius, radius + 1):
                region_x = int(
                    round(keypoint.pt[0] / np.float32(2**octave_index))) + j
                if region_x > 0 and region_x < image_shape[1] - 1:
                    # 差分求偏导,这里省略了1/2的系数
                    dx = gaussian_image[region_y, region_x +
                                        1] - gaussian_image[region_y,
                                                            region_x - 1]
                    dy = gaussian_image[region_y - 1,
                                        region_x] - gaussian_image[region_y +
                                                                   1, region_x]
                    # 梯度模值
                    gradient_magnitude = np.sqrt(dx * dx + dy * dy)
                    # 梯度方向
                    gradient_orientation = np.rad2deg(np.arctan2(dy, dx))
                    weight = np.exp(weight_factor * (i**2 + j**2))
                    # 梯度幅值需先乘以高斯权重再累加到直方图中去
                    histogram_index = int(
                        round(gradient_orientation * num_bins / 360.))
                    raw_histogram[histogram_index %
                                  num_bins] += weight * gradient_magnitude

    for n in range(num_bins):
        # 使用平滑公式
        smooth_histogram[n] = (
            6 * raw_histogram[n] + 4 *
            (raw_histogram[n - 1] + raw_histogram[(n + 1) % num_bins]) +
            raw_histogram[n - 2] + raw_histogram[(n + 2) % num_bins]) / 16.
    orientation_max = max(smooth_histogram)
    # 找出主方向
    orientation_peaks = where(
        np.logical_and(smooth_histogram > roll(smooth_histogram, 1),
                       smooth_histogram > roll(smooth_histogram, -1)))[0]
    for peak_index in orientation_peaks:
        peak_value = smooth_histogram[peak_index]
        # 辅方向,阈值为80%
        if peak_value >= peak_ratio * orientation_max:
            left_value = smooth_histogram[(peak_index - 1) % num_bins]
            right_value = smooth_histogram[(peak_index + 1) % num_bins]
            # 梯度直方图抛物线插值
            interpolated_peak_index = (
                peak_index + 0.5 * (left_value - right_value) /
                (left_value - 2 * peak_value + right_value)) % num_bins
            orientation = 360. - interpolated_peak_index * 360. / num_bins
            if abs(orientation - 360.) < float_tolerance:
                orientation = 0
            new_keypoint = cv2.KeyPoint(*keypoint.pt, keypoint.size,
                                        orientation, keypoint.response,
                                        keypoint.octave)
            keypoints_with_orientations.append(new_keypoint)
    return keypoints_with_orientations
################
#对关键点进行比较#
################
def compareKeypoints(keypoint1, keypoint2):
    """
    keypoint1、keypoint2:需要比较的两个关键点
    """
    # 关键点的点坐标
    if keypoint1.pt[0] != keypoint2.pt[0]:
        return keypoint1.pt[0] - keypoint2.pt[0]
    if keypoint1.pt[1] != keypoint2.pt[1]:
        return keypoint1.pt[1] - keypoint2.pt[1]
    # 关键点邻域直径大小
    if keypoint1.size != keypoint2.size:
        return keypoint2.size - keypoint1.size
    # 角度,表示关键点的方向,值为[零,三百六十),负值表示不使用
    if keypoint1.angle != keypoint2.angle:
        return keypoint1.angle - keypoint2.angle
    # 响应强度
    if keypoint1.response != keypoint2.response:
        return keypoint2.response - keypoint1.response
    # 从哪一层金字塔得到的此关键点
    if keypoint1.octave != keypoint2.octave:
        return keypoint2.octave - keypoint1.octave
    return keypoint2.class_id - keypoint1.class_id
################
#排序并删除重复项#
################
def removeDuplicateKeypoints(keypoints):
    """
    keypoints:关键点
    """
    if len(keypoints) < 2:
        return keypoints
    # 进行排序
    keypoints.sort(key=cmp_to_key(compareKeypoints))
    unique_keypoints = [keypoints[0]]
    # 删除重复值
    for next_keypoint in keypoints[1:]:
        last_unique_keypoint = unique_keypoints[-1]
        if last_unique_keypoint.pt[0] != next_keypoint.pt[0] or \
           last_unique_keypoint.pt[1] != next_keypoint.pt[1] or \
           last_unique_keypoint.size != next_keypoint.size or \
           last_unique_keypoint.angle != next_keypoint.angle:
            unique_keypoints.append(next_keypoint)
    return unique_keypoints
####################################
#将关键点从基本图像坐标转换为输入图像坐标#
####################################
def convertKeypointsToInputImageSize(keypoints):
    """
    keypoints:关键点
    """
    converted_keypoints = []
    for keypoint in keypoints:
        keypoint.pt = tuple(0.5 * np.array(keypoint.pt))
        keypoint.size *= 0.5
        keypoint.octave = (keypoint.octave & ~255) | (
            (keypoint.octave - 1) & 255)
        converted_keypoints.append(keypoint)
    return converted_keypoints
#############
#“解压”关键点#
############
def unpackOctave(keypoint):
    """
    计算每一个关键点的octave、layer和scale
    """
    octave = keypoint.octave & 255
    layer = (keypoint.octave >> 8) & 255
    if octave >= 128:
        octave = octave | -128
    scale = 1 / np.float32(1 << octave) if octave >= 0 else np.float32(
        1 << -octave)
    return octave, layer, scale
####################
#为每个关键点生成描述符#
####################
def generateDescriptors(keypoints,
                        gaussian_images,
                        window_width=4,
                        num_bins=8,
                        scale_multiplier=3,
                        descriptor_max_value=0.2):
    """
    keypoints:关键点
    gaussian_images:高斯金字塔图像
    window_width:关键点附近的区域长为4,4X4个子区域
    num_bins:8个方向的梯度直方图
    scale_multiplier:
    descriptor_max_value:
    """
    descriptors = []

    for keypoint in keypoints:
        # 进行“解压缩”
        octave, layer, scale = unpackOctave(keypoint)
        # 关键点所对应的高斯金字塔图像
        gaussian_image = gaussian_images[octave + 1, layer]
        # 该图像的尺寸
        num_rows, num_cols = gaussian_image.shape
        # 定位
        point = np.round(scale * np.array(keypoint.pt)).astype('int')
        # 为方便后面计算的变量
        bins_per_degree = num_bins / 360.
        # 为方便后面旋转
        angle = 360. - keypoint.angle
        cos_angle = np.cos(deg2rad(angle))  # 角度转弧度
        sin_angle = np.sin(deg2rad(angle))  # 角度转弧度
        # Lowe 建议子区域的像素的梯度大小按0.5d的高斯加权计算
        weight_multiplier = -0.5 / ((0.5 * window_width)**2)
        row_bin_list = []
        col_bin_list = []
        magnitude_list = []
        orientation_bin_list = []
        histogram_tensor = np.zeros(
            (window_width + 2, window_width + 2, num_bins))  # 前两个维度增加2
        # 把3sigma长度作为一个单元长度
        hist_width = scale_multiplier * 0.5 * scale * keypoint.size
        # 实际计算所需的图像区域半径(根据公式)
        # 说明一下,这里就是一个大圆外套一个正方形
        half_width = int(
            np.round(hist_width * np.sqrt(2) * (window_width + 1) *
                     0.5))  # sqrt(2)对应于像素的对角线长度
        # 最终区域长度
        half_width = int(min(half_width, sqrt(num_rows**2 + num_cols**2)))

        # 坐标轴旋转至主方向
        for row in range(-half_width, half_width + 1):
            for col in range(-half_width, half_width + 1):
                row_rot = col * sin_angle + row * cos_angle  # 旋转后的特征点坐标
                col_rot = col * cos_angle - row * sin_angle  # 旋转后的特征点坐标
                # 计算旋转后的特征点落在子区域的下标
                # 坐标归一化
                # +(d/2)是把坐标系由特征点处平移至左上角的边界点
                # -0.5则是回移坐标系至描述子区间中的第一个子区间的中心处
                row_bin = (row_rot / hist_width) + 0.5 * window_width - 0.5
                col_bin = (col_rot / hist_width) + 0.5 * window_width - 0.5
               
                if row_bin > -1 and row_bin < window_width and col_bin > -1 and col_bin < window_width:
                    window_row = int(np.round(point[1] + row))
                    window_col = int(np.round(point[0] + col))
                    if window_row > 0 and window_row < num_rows - 1 and window_col > 0 and window_col < num_cols - 1:
                        # 求偏导
                        dx = gaussian_image[window_row, window_col +
                                            1] - gaussian_image[window_row,
                                                                window_col - 1]
                        dy = gaussian_image[window_row - 1,
                                            window_col] - gaussian_image[
                                                window_row + 1, window_col]
                        # 模值
                        gradient_magnitude = np.sqrt(dx * dx + dy * dy)
                        # 方向
                        gradient_orientation = np.rad2deg(arctan2(dy,
                                                                  dx)) % 360
                        # 高斯加权值
                        weight = np.exp(weight_multiplier *
                                        ((row_rot / hist_width)**2 +
                                         (col_rot / hist_width)**2))
                        
                        row_bin_list.append(row_bin)
                        col_bin_list.append(col_bin)
                        magnitude_list.append(weight * gradient_magnitude)
                        orientation_bin_list.append(
                            (gradient_orientation - angle) * bins_per_degree)

        for row_bin, col_bin, magnitude, orientation_bin in zip(
                row_bin_list, col_bin_list, magnitude_list,
                orientation_bin_list):
            # 通过三线性插值平滑
            # 实际上是在做三线性插值的逆(取立方体的中心值,并将其分配给它的八个邻域)
            row_bin_floor, col_bin_floor, orientation_bin_floor = floor(
                [row_bin, col_bin, orientation_bin]).astype(int)
            # 计算差值部分,小数余项
            row_fraction, col_fraction, orientation_fraction = row_bin - row_bin_floor, col_bin - col_bin_floor, orientation_bin - orientation_bin_floor
            if orientation_bin_floor < 0:
                orientation_bin_floor += num_bins
            if orientation_bin_floor >= num_bins:
                orientation_bin_floor -= num_bins

            c1 = magnitude * row_fraction
            c0 = magnitude * (1 - row_fraction)
            
            c11 = c1 * col_fraction
            c10 = c1 * (1 - col_fraction)
            c01 = c0 * col_fraction
            c00 = c0 * (1 - col_fraction)
            # 最终累加在每个方向上的梯度大小为
            c111 = c11 * orientation_fraction
            c110 = c11 * (1 - orientation_fraction)
            c101 = c10 * orientation_fraction
            c100 = c10 * (1 - orientation_fraction)
            c011 = c01 * orientation_fraction
            c010 = c01 * (1 - orientation_fraction)
            c001 = c00 * orientation_fraction
            c000 = c00 * (1 - orientation_fraction)

            histogram_tensor[row_bin_floor + 1, col_bin_floor + 1,
                             orientation_bin_floor] += c000
            histogram_tensor[row_bin_floor + 1, col_bin_floor + 1,
                             (orientation_bin_floor + 1) % num_bins] += c001
            histogram_tensor[row_bin_floor + 1, col_bin_floor + 2,
                             orientation_bin_floor] += c010
            histogram_tensor[row_bin_floor + 1, col_bin_floor + 2,
                             (orientation_bin_floor + 1) % num_bins] += c011
            histogram_tensor[row_bin_floor + 2, col_bin_floor + 1,
                             orientation_bin_floor] += c100
            histogram_tensor[row_bin_floor + 2, col_bin_floor + 1,
                             (orientation_bin_floor + 1) % num_bins] += c101
            histogram_tensor[row_bin_floor + 2, col_bin_floor + 2,
                             orientation_bin_floor] += c110
            histogram_tensor[row_bin_floor + 2, col_bin_floor + 2,
                             (orientation_bin_floor + 1) % num_bins] += c111

        descriptor_vector = histogram_tensor[1:-1,
                                             1:-1, :].flatten()  # 删除直方图边界
        # 设定阈值,并归一化描述符
        threshold = norm(descriptor_vector) * descriptor_max_value
        descriptor_vector[descriptor_vector > threshold] = threshold
        descriptor_vector /= max(norm(descriptor_vector), float_tolerance)

        descriptor_vector = np.round(512 * descriptor_vector)
        descriptor_vector[descriptor_vector < 0] = 0
        descriptor_vector[descriptor_vector > 255] = 255
        descriptors.append(descriptor_vector)

    return array(descriptors, dtype='float32')
##########主函数###############
##############################
#计算输入图像的SIFT关键点和描述符#
##############################
def computeKeypointsAndDescriptors(image,
                                   sigma=1.6,
                                   num_intervals=3,
                                   assumed_blur=0.5,
                                   image_border_width=5):
    """
    image:输入图像
    sigma:目标高斯模糊度
    num_intervals:能进行极值点检测的图层数
    assumed_blur:假设模糊度
    image_border_width:图像边缘5个像素不检测
    """
    image = image.astype(np.float32)
    # 升采样生成基图像(为了尽可能多地保留原始图像信息,对原始图像进行扩大两倍采样)
    base_image = generateBaseImage(image, sigma, assumed_blur)
    # 计算可以将图像重复减半直到变得很小的次数
    num_octaves = computeNumberOfOctaves(base_image.shape)
    # 生成高斯模糊度列表,以产生尺度金字塔
    gaussian_kernels = generateGaussianKernels(sigma, num_intervals)
    # 生成高斯金字塔
    gaussian_images = generateGaussianImages(base_image, num_octaves,
                                             gaussian_kernels)
    # 生成高斯差分金字塔
    dog_images = generateDoGImages(gaussian_images)
    # 寻找关键点
    keypoints = findScaleSpaceExtrema(gaussian_images, dog_images,
                                      num_intervals, sigma, image_border_width)
    # 对关键点进行去重处理
    keypoints = removeDuplicateKeypoints(keypoints)
    # 将关键点从基本图像坐标转换为输入图像坐标
    keypoints = convertKeypointsToInputImageSize(keypoints)
    # 为关键点生成描述符
    descriptors = generateDescriptors(keypoints, gaussian_images)
    return keypoints, descriptors
kp1, des1 = computeKeypointsAndDescriptors(img1)
# 左图特征点可视化
fig = plt.figure()
ax =fig.add_subplot(111)
plt.imshow(img1, cmap='gray')
for i in range(len(kp1)):
   ax.plot(kp1[i].pt[0], kp1[i].pt[1], '.', color = 'red')
plt.show()

在这里插入图片描述

kp2, des2 = computeKeypointsAndDescriptors(img2)
# 右图特征点可视化
fig = plt.figure()
ax =fig.add_subplot(111)
plt.imshow(img2, cmap='gray')
for i in range(len(kp2)):
   ax.plot(kp2[i].pt[0], kp2[i].pt[1], '.', color = 'blue')

在这里插入图片描述

imageA = img2 # 右图
imageB = img1 # 左图
kpsA = kp2
kpsB = kp1 # 特征点
featuresA = des2
featuresB = des1 # 特征向量
kpsA = np.float32([kp.pt for kp in kpsA]) # 类型转换
kpsB = np.float32([kp.pt for kp in kpsB])
###########
#全景图生成#
##########
class Stitcher:
    # 拼接函数
    def stitch(self, images, ratio=0.75, reprojThresh=4.0, showMatches=False):
        # 获取输入图片
        (imageA, imageB) = images

        # 匹配两张图片的所有特征点,返回匹配结果
        M = self.matchKeypoints(kpsA, kpsB, featuresA, featuresB, ratio,
                                reprojThresh)

        # 如果返回结果为空,没有匹配成功的特征点,退出算法
        if M is None:
            return None

        # 否则,提取匹配结果
        # H是3x3视角变换矩阵
        (matches, H, status) = M
        # 将图片A进行视角变换,result是变换后图片
        result = cv2.warpPerspective(
            imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0]))
        self.cv_show('result', result)
        # 将图片B传入result图片最左端
        result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB
        self.cv_show('result', result)
        # 检测是否需要显示图片匹配
        if showMatches:
            # 生成匹配图片
            vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status)
            # 返回结果
            return (result, vis)

        # 返回匹配结果
        return result

    def cv_show(self, name, img):
        cv2.imshow(name, img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

    def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio,
                       reprojThresh):
        # 建立暴力匹配器
        matcher = cv2.BFMatcher()
        
        # 使用KNN检测来自A、B图的SIFT特征匹配对,K=2
        rawMatches = matcher.knnMatch(featuresA, featuresB, 2)  # 检测出每个点,匹配的2个点
        # 返回的M结果为[(1, 6), ..,(112, 113)]等等,里面的数字为第几个特征点
        matches = []
        for m in rawMatches:
            # 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对
            if len(m) == 2 and m[0].distance < m[1].distance * ratio:
                # 存储两个点在featuresA, featuresB中的索引值
                matches.append((m[0].trainIdx, m[0].queryIdx))

        # 当筛选后的匹配对大于4时,计算视角变换矩阵
        if len(matches) > 4:
            # 获取匹配对的点坐标(float32型)
            ptsA = np.float32([kpsA[i] for (_, i) in matches])
            print(ptsA.shape)  # (148, 2)
            ptsB = np.float32([kpsB[i] for (i, _) in matches])

            # 计算视角变换矩阵(把RANSAC和计算H矩阵合并到了一起)
            (H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC,
                                             reprojThresh)
            # 该函数的作用就是先用RANSAC选择最优的四组配对点,再计算H矩阵。H为3*3矩阵
            print(status.shape)
            # 返回结果
            return (matches, H, status)

        # 如果匹配对小于4时,返回None
        return None

    def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status):
        # 初始化可视化图片,将A、B图左右连接到一起
        (hA, wA) = imageA.shape
        (hB, wB) = imageB.shape
        vis = np.zeros((max(hA, hB), wA + wB), dtype="uint8")
        vis[0:hA, 0:wA] = imageA
        vis[0:hB, wA:] = imageB

        # 联合遍历,画出匹配对
        for ((trainIdx, queryIdx), s) in zip(matches, status):
            # 当点对匹配成功时,画到可视化图上
            if s == 1:
                # 画出匹配对
                ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
                ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
                cv2.circle(vis, ptA, 5, (0, 0, 255), 1)
                cv2.circle(vis, ptB, 5, (0, 0, 255), 1)
                cv2.line(vis, ptA, ptB, (0, 0, 255), 1)

        # 返回可视化结果
        return vis
# 对右边的图形做变换
# 把图片拼接成全景图
stitcher = Stitcher()
(result, vis) = stitcher.stitch([imageA, imageB], showMatches=True)

# 显示所有图片
cv2.imshow("Image A", imageA)
cv2.imshow("Image B", imageB)
cv2.imshow("Keypoint Matches", vis)
cv2.imshow("Result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

完整代码我已放到我的资源下载中心,田纳尔多,可以在上面下载。

4 实验结果与分析

1、原始图像

左图 右图
在这里插入图片描述 在这里插入图片描述

2、两张图像的特征点匹配

在这里插入图片描述

3、右图作了变形的结果

在这里插入图片描述

4、拼接结果

在这里插入图片描述

可以看出两张图片已经被连接在了一起,图片间没有明显的分割与错位,整体上也没有照片之间的独立感。连续的拼接需要右侧的图像不断被仿射变化来与左侧图像连接,而导致了最右侧的图像在最终的全景图中有些扭曲。整体来说,还算成功!


5 参考内容

  1. SIFT特征点提取
  2. sift算法详解及应用课件
  3. 翻译:图像拼接
  4. Lowe原文
  5. 线性插值与抛物线插值
  6. SIFT特征分析与源码解读
2015-04-27 23:17:02 lxw907304340 阅读数 5040
  • Linux系统目录及必备命令学习

    通过本课程的学习,大家可以从懵懂到熟悉,从熟悉到熟练,能够在企业中熟练运用,同时在学习的过程中有任何不明白的地方,都可以向我咨询,我会积极帮助大家解决问题。

    217人学习 吴光科
    免费试看


http://blog.csdn.net/byxdaz/article/details/3972293

目前比较出名的图像处理库有很多,比如LEADTOOLS,这个是功能非常强大的图像多媒体库,但是这个是收费注册的。开源的图像库也有不少,比如:ImageStone、GIMP、CxImage等,虽然它们的功能没有LEADTOOLS强大,但是一般的图像处理是可以应付的。

下面分别介绍这几种图像处理库的使用方法。

LEADTOOLS

LEAD Technologies 是一个拥有九年多开发数字图形技术历史的公司,他专为程序开发者提供软件开发工具包。他已经为微软等公司提供了很多图形图象技术。这个软件包是该公司开发的开发工具的集合,包括占线帮助,Lead API、C++ 类库、ActiveX 控件、VCLs 和一些实例原代码等。这个软件包是开发工具的集合包括占线帮助,Lead API、C++ 类库、ActiveX 控件、VCLs 和一些实例原代码等。

(1)对显示设备的全面支持:在显示时,你的程序中无需考虑是哪种显示模式,如16色还是真彩色。LeadTools为你做了所有的事。当然你也可以通过设置参数获得对显示设备更多的控制。你还可以实现自己的调色板。

(2)支持多种文件格式:表10.1是LeadTools所支持的常用文件格式,其中读表示用LeadTools能打开的文件格式,写表示LeatTools能存成的文件格式:

(3)图象处理:如二值化、平滑、加噪声、增加对比度、色调、饱和度、亮度、Gamma校正、中值滤波、半影调、抖动、橡皮筋、滚动、填充、反色、镜象、马赛克、浮雕、打印、扫描、拷贝、粘贴、裁剪、缩放、截屏、调色板、直方图、有关数据库的操作、还有制作幻灯的功能,如淡入、淡出、卷帘等等、真的很爽。表10.2是Version5.1的所有属性、事件、和方法列表,看看有没有你所需要的功能。以字母顺序排列,其中标[P]的表示只有专业级(Professional)和特殊级(Express)用户才能使用。

(4) 新增功能:

Vector的新功能

Annotations的新对象和新功能

OCR Module 更新

新增模块:ICR Module

新增模块:OMR Module (Forms 处理)

新增的公共图象对话框

100余种图象处理功能

DjVu® KDC和其他的新格式支持

Medical Imaging 更新

Multimedia 更新

C++ Class Lib 更新

COM Object 更新,包含新的.NET 示例

相关包的破解地址:http://www.greatcracks.com/cracked_software/l4

相关开发包介绍:

LEADTOOLS Medical Imaging SDK

LEADTOOLS Medical Imaging包含了一些精心挑选的、经过优化的特性,可以满足医疗成像应用程序开发的特殊需要,比如,完全的DICOM数据集支持,可以支持8-16位灰度级,以及12和16位的图像处理,窗位(Window Leveling)显示,还包含LUT处理。

特征综述

支持8、10、12、16灰度级和24位图像,包括JPEG(8、10、12、16位无损灰度级,8、12位有损灰度级,24位无损和有损色彩),JPEG2000(包括有损和无损8-16位灰度级图像和24位色彩)行程,无压缩,光学表示(单色1、单色2、调色板、RGB、ARGB、CMYK、YBR 全422、YBR全、YBR_RCT、YBR_ICT),以及单框架和多框架图像。

完全支持DICOM 3(2003文件规范),包括所有标准的IOD类和形态(CR、CT、MR、NM、US、RF、SC、VL等),还包含VR’s。

为DICOM的基本目录提供简单的执行操作和完全技术支持。只需要几行代码就可以使用高级COM对象来创建DICOMDIR。

通过调用一个函数,可以将一种转移语法变为另一种转移语法。自动识别DICOM文件(Little-Endian/Big-Endian,隐藏/显示VR等等)。并具有高级和低级函数,可用于数据集的简单导航和操作(插入、删除、修改)。

单框架或多框架的图像(压缩的或未压缩的)。

对大量不同厂商的硬件支持。

包含了可使用的源代码和可执行代码。

对DICOM有4个不同的编程接口-API、COM、VCL、C++类库。支持DICOM2003标准中所有的VR’s,包括“其它浮动字符串”。

具有高级和低级函数,可用于数据集的简单导航和操作(插入、删除、修改)。

LEADTOOLS自动加入了项定界项和序列定界项以简化数据集的嵌套(VR of “SQ”)。

易于对项进行顺序定位(FFFE,E000)。

简化了重组处理(多重覆盖和曲线数据)。

支持对单框架或多框架压缩或未压缩图像的像素数据的封装或本机编码。 
DICOM LUT/Overlay/Palette支持。在LEADTOOLS Medical Imaging中,对于Modality

LUT,VOI LUT,Palette色彩图像的高级支持意味着开发人员可以毫不费力地在一个应用程序中实现DICOM表示状态。LEADTOOLS还可以将Modality LUT应用于图像数据,以进一步简化图像处理和统计计算。

DICOM注释对象支持。可以从数据集内存储/获取DICOM注释,并且将它们转换为LEAD注释对象,反之亦然。为了附加的统计计算和存储为DICOM表示状态,注释也可以被转换为Region of interest。
Cookie剪切工具:可以从一个放射性图像扫描文件中提取出单独的薄片(对于识别一个扫描MRI或CT系列内的每个薄片很有帮助)。

MRI大脑分析工具:可以用于计算胼胝体(大脑中部的白色物质,就像字母X)和脑球体之间的比率。

LEADTOOLS Multimedia

您可以使用LEADTOOLS Multimedia SDK创建具有专业水准的高品质的多媒体应用程序。该控件中加入了诸如捕获、播放和编辑等多媒体功能以及对最新的DirectShow过滤和许多多媒体文件格式的支持,即可以加载和转换多种文件格式(包括WAV、AVI、ASF、WMA、WMV、MPEG-1、OGG等等)。可以编程控制多媒体处理、压缩,视频和音频输入;还增加了摄像控制(亮度、对比度、缩放等等);以及更灵活的捕获选项等等更多功能。

特征综述 About Feature

多媒体捕获
能够从WDM、本地DirectShow包括DV、Windows视频捕获设备中捕获视频图像,还可以控制DV设备等。

多媒体播放
能够播放所有DirectShow所支持的文件。可以支持DVD导航,还可以将播放设置保存到文件或二进制流中等等。

多媒体转换
利用控件包提供的功能,您的用户可以将多媒体文件在AVI,Windows Media content,WAVE和MPEG1 Audio(MP3)格式之间互相转换,还可以选择DV设备输出等等功能。

多媒体文件格式
支持大部分流行的多媒体格式(AVI、ASF、WMV、WAV、OGG、MPEG-2、MPG)。点击此处可以得到一个所支持格式的列表。

多媒体压缩
通过可用的DirectShow过滤器压缩多媒体文件(内存中进行)。(Windows Media)

多媒体处理
具有处理AVI、Windows Media content、WAVE和MPEG1Audio(MP3)格式或DirectShow过滤器捕获到的数据的能力。在电影回放、转换或被捕获时可以使用LEAD Video callback DirectShow过滤器中的200种像处理功能来处理电影。

DirectShow过滤器
LEADTOOLS Multimedia包含了一个多媒体过滤器包的测试版,这个多媒体过滤器包具有强大的多媒体处理功能。

产品演示
LEADTOOLS Multimedia具有一些演示程序,可以帮助您开发您的项目。其中有音频、视频检测demo,音频更换demo,Mediainfo demo等等。

LEADTOOLS Raster Imaging SDK

LEADTOOLS Raster Imaging SDK是为开发者提供的用于创建功能强大的图像应用程序工具;采用LEADTOOLS的色彩转换、显示、压缩(JPEGTIFFCCITT G4 以及LEADCMP属性)、图象处理、TWAIN扫描、特效(超过2000种)、图像格式支持(超过150种图像文件格式)、打印、Internet/intranet 图像、数据库图像、图像公共对话框和屏幕捕获等特性,使用它,由此为你节省大量的时间与费用。

特征综述 About Feature

文件格式支持
采用工业标准和专有压缩技术,可同时支持150多种图像文件格式以及sub-格式的加载、保存和转换。另外,采用LEADTOOLS能添加更多的格式支持,例如采用LEADTOOLS PDF插件,即能实现对PDF文件的加载、保存和转换支持。

压缩支持
支持多种工业标准的压缩技术,包括JPEG、CCITT、LZW (使用于GIF及某些TIFF文件)、ZIPLIB (使用于PNG文件)、Huffman算法、RLE及紧缩位压缩算法(Packbits)。另外,采用LEADTOOLS JPEG2000 插件和 LEADTOOLS CMW 插件可添加JPEG2000 及先进的CMW压缩技术。

TWAIN扫描支持
TWAIN 驱动几乎包含在所有的扫描仪及其它图像采集设备中,同时随着最新的发展技术,TWAIN 也是性能良好与高可靠性驱动的最佳选择。当前LEADTOOLS 的光栅图像产品支持所有包括在TWAIN v1.9 规范中的功能。

屏幕捕获
具有强大的屏幕捕获功能,能从文件、菜单、窗口、不同形状的屏幕客户区域等捕获资源。屏幕捕获工具是极其有用的,特别是在文档编辑及需提供其它图像输入方式的时显得更为重要。

数据库支持
同时提供高端或低端的数据库支持,其中包括绑定VB数据控件功能、OLEDB 接口、ODBC 接口以及低端加载和保存图像到内存的功能。

Internet 支持
提供几种不同级别Internet支持,包括:可封装到CAB文件的对象、能在web服务器端构造的对象、从URL或存储器装载图像的功能、支持HTTP 和FTP的控件、解析并加载到web服务器端的文件控件,以及用来构造功能强大、通过TCP/IP发送远程过程调用的客户服务器应用程序控件。

显示与显示效果
对图像如何被显示进行完全地控制,包括缩放、展开、图像显示的区域控制以及超过2000种的特效效果。LEADTOOLS 甚至包含了一个平铺窗口,这样你能方便地显示图像概要及选用适合最终用户的卷起、平铺方案。

打印支持
由于能使用与显示图像同样的代码来进行打印,所以打印图像与显示图像同样容易。你能控制图像打印的尺寸和位置;另外,能结合Windows GDI函数同LEADTOOLS打印一起使用,提供在同一页上打印文本和多个图像的灵活性。

图像处理
超过200种图象处理功能,分为四个基本类别:过滤、变换、色彩转换及绘图。在LEADTOOLS中的大多数图像处理功能支持不同着重区域,允许图像的某一部份被处理。通过添加更多用于数字绘图的功能,LEAD在LEADTOOLS Raster Imaging Pro中提供了包含大量用于数字绘图功能,包括笔刷、形状、纹理的创建与使用以及更多。

色彩转换
为确保你能把图像从一种格式转换到另一种格式,或是在任何其它显示设备上显示图象,LEADTOOLS 包含了强大的色彩转换功能。该色彩转换功能包括:支持8 种以上色彩平滑过渡算法、多种调色板选项以及把图像数据从任何支持的每个像素所占用的位转换到其他支持的每个像素所占用的位功能。支持的数值有1-8位色或灰度、16位色、24 位色和32 位色。如果你需要12或16位灰度或48与64位色支持。

图像公共对话框
LEADTOOLS 图像公共对话框通过扩展Windows公共对话框以提供具体的图像处理功能,节省了大量的繁琐编程时间,同时能给最终用户提供一个一致的外观和感觉。这个公共对话框针对图像处理、变换及特效提供专业的图像公共对话框功能。

LEADTOOLS Vector Imaging Pro

LEADTOOLS Vector Imaging Pro控件包含了创建功能强大的,支持矢量和光栅图像格式的2-D3-D浏览器、编辑器、转换器的一切所需要求。通过创建这样的应用程序,您不需要安装或注册一些更昂贵的用于创建矢量文件的原始程序,您只需添加您所需要的功能。如果您在为矢量成像寻找具有最丰富特色的软件开发工具包时,LEADTOOLS Vector Imaging Pro也许是您最好的选择。

特征综述 About Feature

支持矢量图像格式
矢量图像可以被加载、保存、转换和输出为光栅图像格式,包括CGM、DGN、DRW、DWF、DXF、EMF、WMF、PLT、Gerber、PCL、PCT、SVG、VWPG、CMX和SHP。

支持原始对象
支持原始对象,包括圆弧、位图、笔刷、照相机、圆、复制、椭圆、椭圆弧、字体、组、层、线、笔、饼、弦、Poly Draw、多边形、多边线、Poly Bezier、光栅、长方形、文本、顶点和Clipping对象。

支持浏览和编辑
具有低级和高级函数,可以控制矢量图像的浏览,包括缩放(统一的或围绕任何轴线)、移动镜头、旋转(围绕任何轴线)和抗失真。矢量图形可以以任何希望的方式编辑,包括添加、修改、删除和将对象复制到粘贴板或者复制到各个不同的矢量图形中。

支持打印
可以把矢量图形缩小到任何分辨率而不会使图形出现扭曲,这样就可以确保在打印时,图形细节不会丢失。

支持矢量转换
不需要创建文件的原始程序就可以将矢量文件转换为另一种格式。

支持光栅图像
矢量文件可以被转换为任何分辨率的光栅图像,并且可以被保存为LEADTOOLS Raster Imaging 产品支持的任何150种图像文件格式。

LEADTOOLS 软件开发工具包也包括了常见的光栅成像特色包括图像处理(转换、过滤、绘制、Region of Internet),色彩转换,显示,特效(从2000多种效果中选出的),压缩,图像格式(导入/导出),打印,Internet/Intranet成像,数据库成像,成像对话框和屏幕捕获。

LEADTOOLS现在还支持Unicode编码
为了向大量的开发人员提供矢量成像功能,控件包提供了5个编程接口:低层次的API、C++类库,以及易于使用但功能强大的ActiveX、COM、VCL控件。LEADTOOLS Vector Imaging Pro带有示例源代码,适用于Visual Basic, C/C++, Visual C++ (MFC), C++ Builder, Visual J++, Visual FoxPro, Access, Delphi, and VB and JavaScript,并且支持Visual Studio 6.0数据库连接(Oracle, SQL, OLE DB, ODBC和JET)。

LEADTOOLS Barcode Module

LEADTOOLS Barcode Module产品允许程序员很容易地在应用软件中加入编码,这样就可以读写Linear1D)、DataMatrix (2D)PDF417(2D)QR编码的条形码——包括对41种不同子类型的支持。LEADTOOLS Barcode Modules还加入了LEADTOOLS文档和医学图像工具包。

特征综述 About Feature

Barcode Modules :
设计Linear (1D), DataMatrix (2D), PDF 417 (2D)和QR Code (2D) 条形码样式

可以在任何方向上读出多个条形码。

可以将条形码写入任何超过150种图像文件格式或者写入任何服从窗口的打印机。

利用2D条形码技术,每个条形码符号可用数以千计的字符来编码。

可以从选定目标区域内读出或写入条形码。

可以读出指定颜色的条形码。

可以写入指定颜色的条形码。

使LEADTOOLS的广泛的图像处理技术一体化,以便完全控制用户的条形码识别过程。

下表列出了每个模块的特点:

读取/写入1D Symbols条形码

高级的/标准的

自动条形码类型检测。

从目标区域读出或者写入条形码。

可以从不同的方向读出,比如从左到右,从右到上,对角线方向等等。

读取倾斜的条形码符号。

可以读出多个条形码符号。

可以读出混合的条形码类型,并以x/y的形式来返回报告。

可以写入条形码符号,条形码符号带有大小和位置参数。

可以检验字符处理。

读取/写入2D DataMatrix条形码
包括差错检测码(ECC),能够修复被破坏的条形码符号。

读出或者写入彩色和灰色的条形码。

可以在一个条形码符号中存储多达2218个ASCII字符,还可以把符号串接起来,用多达33225个字符对其编码。

(读取模块)可以读出条形码,条形码是独立于歪斜失真、方向和取向改变之外的。从目标区域读出条形码,条形码包含在任何支持的图像文件格式中(支持类型超过150种),并可从扫描仪或者数字照相机获取。

(写入模块)可以写入任何支持的光栅格式(超过150种)到目标区域,也可以打印到任何支持Windows的打印机上。

读出/写入2D PDF 417 条形码
可以在一个条形码符号种存储多达1815个ASCII字符。

包括了差错检测码(ECC),能够修复被破坏的条形码符号。

(读取模块)可以读出条形码,条形码是独立于歪斜失真、方向和取向改变之外的。从目标区域读出条形码,条形码包含于任何支持的图像文件格式中(支持类型超过150种),并可从扫描仪或者数字照相机获取。

(写入模块)可以写入任何支持的光栅格式(超过150种)到目标区域,也可以打印到任何支持Windows的打印机上。

读出/写入2D QR Code Modules
可以从ALPHA或者数字文本中创建QR编码的条形码符号。

包含差错检测码(ECC),它可以排除可能出现的被破坏的读出数据。

可以在一个条形码符号中存储多达2335个ASCII字符。

可以使用差错检测码(ECC)来修复被破坏的条形码符号。

可以读出独立于歪斜的、定位的、取向改变和反射之外的条形码。

可以将QR编码条形码符号输出到任何支持的Windows打印机上。

ADTOOLS Document Imaging SDK

LEADTOOLS Document Imaging 是一套可以扫描多种语言文档的COM和VCL控件。它可以执行光学字符识别,并且将扫描出的文本以四十多种不同的格式输出,包括MS Word,MS Excel,Dbase和WordPerfect等等。当它用于格式识别和处理应用时,可以加快光符识别处理速度。

注释
本产品具有强大的注释功能,可以添加文本、高亮、附注、音频、椭圆、圆、方形、按钮、线条、箭头、长方形、多边形、修订(中断信息)、热点、手写字、指针、图像、图章、标尺和超级链接,以上所有对于文档、色彩和灰度图像都具有多层安全特性。文档成像(Document Imaging)和文档成像套装(Document Imaging Suite)产品都具有此注释功能。

显示,处理以及内存优化
本产品不仅包含了光栅成像产品的所有显示功能,还拥有被称为双色缩放比例的专业化显示选项,此功能能够大大的提高成像图像的质量以及屏幕上黑白图像的可读性。

图像获取 – TWAIN捕获
TWAIN驱动是几乎所有扫描器和其它图像获取设备的标准,再加上技术的进一步完善,因此我们的LEADTOOLS Fast TWAIN产品绝对是您在性能和可靠性方面的最佳选择。目前LEADTOOLS 文档成像(LEADTOOLS Document Imaging)产品支持TWAIN 1.9规范中的所有功能。

图像增强功能
扫描和传真过程中,噪声和失真不可避免,而它们会导致光符字符识别的精度下降并影响人类阅读。要克服这些缺点,用户只需使用LEADTOOLS文档成像产品中名为Doc-Clean的功能,此功能可以消除成像图像中的噪声、边界、线条和装订孔,平滑文本,反转白色文本为黑色文本,还包含可延伸的倾斜图像(抗扭斜),粗化线条和文本(膨胀)以及腐蚀过厚的文本的其它功能(腐蚀)。

专用存储和压缩功能
Mixed Raster Content(MRC)使用一种技术将图像划分为图像和文本,然后使用一种适合于此类型图像的压缩技术来获取最高的压缩率以及最佳的图像质量。

光学字符识别(OCR)
光学字符识别功能能够将图像转换成编码形式的字符串,或将其内容以各种文档、数据库以及表单的格式保存(更多有关内容,请参考LEADTOOLS OCR功能)。文档成像套装(Document Imaging Suite)产品包含有此功能,如果您使用的是文档成像(Document Imaging)产品,则可以购买一个OCR插件来获取此功能。

条形码
条形码应用于文档成像领域已有多年的历史,它主要用来帮助识别文档,能够包含文档信息以及用于其它用途的各种信息。鉴于条形码在文档成像应用程序中的强大功能,LEAD公司推出了多种强大的条形码扩展功能,您可以购买这些功能来满足您的应用程序的特定需求。

全面成像特征功能
为使LEADTOOLS图像成像系列产品成为用途最广泛和最强大的图像成像产品,LEAD公司开发了全面的(双色、灰度以及彩色)成像特征功能包括图像处理(变换、过滤、绘制),颜色转换、显示、特效(精选自两千多种特效),压缩、(导入/导出的)图像格式,打印、国际互联网/企业内部互联网成像、数据库成像、成像通用对话框以及屏幕抓拍等等。

ImageStone

ImageStone是一套功能强大的C++图像处理库,它可以在多个平台之间移植。  
  功能包括:读写图像文件(JPG,GIF,PNG,TIFF,TGA...),显示,柱状图分析,undo/redo支持,超过100种预定义的特效等。  
下载地址:http://www.codeguru.com/cpp/g-m/bitmap/viewers/article.php/c12577/
  
  里面有全部的源码和详细的帮助文档加9个例子程序,其中example  008是一个比较完善的图像处理程序。
Introduction

ImageStone is a powerful C++ class library for image manipulation. It is written in pure C++ and is easy to port. Its features include load/save (supports BMP, GIF, JPG, PNG, TIF, ICO, TGA, PCX, PSD...), display, histogram, undo/redo, and image transformation with over 100 predefined effects.

License

ImageStone is free. You can use the code however you want (free or commercial), as long as you don't claim it as your own. (If you use it in your product, I hope I could be notified.)

Using ImageStone

It's extremely easy. All you need to do is add #include "ImageStone.h" at the beginning of your source code. If you are using ImageStone in a MFC project, just add this include line at the end of the StdAfx.h file.

The most basic and most important class is FCObjImage. Learn how to use it.

... load/save image file under any OS

// for Windows : ImageStone uses GDI+ to load/save image,
//               Jpg/Png/Gif/Bmp/Tga/Tif be supported
// for Linux : Only Bmp/Tga are supported, but you can set
//             FreeImage handler to support more.
FCObjImage   img ;
img.Load ("test.jpg") ;
if (!img.IsValidImage())
{
assert(false) ;
returnfalse ;
}

// print image's information : width, height, bit per pixel
printf ("image's width : %d",  img.Width()) ;
printf ("image's height : %d", img.Height()) ;
printf ("image's bpp : %d",    img.ColorBits()) ;

// Load/Save function determine image's format by file's ext name
// save image as jpeg file, its quality set 90 (ranges from 1 to 99)
img.Save ("save.jpg", 90) ;
img.Save ("save.png") ;
img.Save ("save.tif") ;

// Another way to set quality
FCImageProperty   prop ;
prop.SetPropertyValue (IMAGE_TAG_JPEG_QUALITY, "90") ;
img.Save ("save.jpg", prop) ;

... load image from memory under any OS

// Load image into memory
char* p = 0 ;
intn = 0 ;
FCOXOHelper::LoadFileToBuffer ("test.jpg", p, n) ;

FCObjImage   img ;
img.Load (p, n, IMG_JPG) ;

delete[] p ;

// this line demonstrates how to determine the image's format by
// the file's ext name
IMAGE_TYPE  t = FCObjImage::GetImageHandleFactory()->
QueryImageFileType("test.jpg");

... load image from DIB stream under any OS

// Load image into memory
char* p = 0 ;
intn = 0 ;
FCOXOHelper::LoadFileToBuffer ("test.bmp", p, n) ;
p += sizeof(BITMAPFILEHEADER) ;

// now p points to a DIB stream
FCObjImage   img ;
img.LoadDIBStream (p, n) ;

delete[] p ;

... load image from a resource under Windows

// Load image from local exe file
FCObjImage   img ;
FCWin32::LoadImageRes (img, MAKEINTRESOURCE(nID), TEXT("JPG"),
IMG_JPG) ;

// Load image from DLL's resource
HMODULE   hDll = LoadLibrary (TEXT("ResDll.dll")) ;
FCWin32::LoadImageRes (img, MAKEINTRESOURCE(nID), TEXT("JPG"),
IMG_JPG, hDll) ;

// Load image from standard BITMAP resource
FCWin32::LoadImageBitmapRes (img, MAKEINTRESOURCE(nID)) ;

... load/save image via FreeImage library

// change to FreeImage library to load/save image
// for more detail, refer to example 005
FCObjImage::SetImageHandleFactory (new FCImageHandleFactory_FreeImage) ;
img.Load ("test.jpg") ;

// change to GDI+ load/save image
FCObjImage::SetImageHandleFactory (new FCImageHandleFactory_Gdiplus) ;
img.Load ("test.jpg") ;

... combine ImageHandleFactory

// use FreeImage to load/save PSD/PCX image
class CMyImageFactory : public FCImageHandleFactory
{
protected:
virtual FCImageHandleBase* CreateImageHandle (IMAGE_TYPE imgType)
{
switch (imgType)
{
case IMG_BMP : returnnew FCImageHandle_Bmp ;
case IMG_TGA : returnnew FCImageHandle_Tga ;
case IMG_JPG : returnnew FCImageHandle_Gdiplus ;
case IMG_GIF : returnnew FCImageHandle_Gdiplus ;
case IMG_TIF : returnnew FCImageHandle_Gdiplus ;
case IMG_PNG : returnnew FCImageHandle_Gdiplus ;
case IMG_PCX : returnnew FCImageHandle_FreeImage ;
case IMG_PSD : returnnew FCImageHandle_FreeImage ;
}
return 0 ;
}
// protected avoid user delete object.
virtual ~CMyImageFactory() {}
};

// use our custom factory to read/write image
FCObjImage::SetImageHandleFactory (new CMyImageFactory) ;
FCObjImage   img ;
img.Load ("test.jpg") ;

... load multi-frame GIF

FCObjMultiFrame   img ;
img.Load ("test.gif") ;
img.GetFrame(0)->Save ("001.jpg") ;
img.GetFrame(1)->Save ("001.jpg") ;
...

... Load a jpeg's EXIF information

FCObjImage        img ;
FCImageProperty   prop ;
img.Load ("test.jpg", &prop) ;

// get camera's ISO speed
std::stringm = prop.QueryPropertyValue (IMAGE_TAG_EXIF_ISOSpeed) ;
// get camera's equip model
std::stringn = prop.QueryPropertyValue (IMAGE_TAG_EquipModel) ;

... draw image object under Windows

FCObjImage   img ;
// capture current screen
RECT         rc = {0, 0, GetSystemMetrics(SM_CXSCREEN),
             GetSystemMetrics(SM_CYSCREEN)} ;
FCWin32::CaptureScreen (img, rc) ;

// Draw image (no stretch) where top-left at (0,0) of hdc
FCWin32::DrawImage (img, hdc, 0, 0) ;

// Stretch image on region of hdc
RECT     rcOnDC = {100, 100, 200, 200} ;
FCWin32::DrawImage (img, hdc, rcOnDC) ;

// Stretch image on central of hdc's region and keep image's aspect
FCWin32::DrawImageAspect (img, hdc, rcOnDC) ;

// Stretch region of image on region of hdc
RECT     rcImg = {20, 20, 50, 50} ;
FCWin32::DrawImage (img, hdc, rcOnDC, rcImg) ;

... copy/paste image to/from Clipboard

FCObjImage   img ;
img.Load ("test.jpg") ;

// copy image to Clipboard
FCWin32::CopyToClipboard (img) ;

// get image on Clipboard
FCWin32::GetClipboardImage (img) ;

... convert between GDI HBITMAP and FCObjImage

// create HBITMAP from FCObjImage object
FCObjImage   img ;
img.Load ("test.jpg") ;
HBITMAP   h = FCWin32::CreateDDBHandle (img) ;

// create FCObjImage object from HBITMAP
FCWin32::CreateImageFromDDB (h, img) ;

... convert between GDI+ bitmap and FCObjImage

// create GDI+ Bitmap from FCObjImage object
FCObjImage   img ;
img.Load ("test.jpg") ;
Gdiplus::Bitmap   * pBmp = FCWin32::GDIPlus_CreateBitmap(img) ;
delete pBmp ;

// create FCObjImage object from GDI+ Bitmap
FCWin32::GDIPlus_LoadBitmap (*pBmp, img) ;

... process image

FCObjImage   img ;
img.Load ("test.jpg") ;

// resize (smooth) image
img.Stretch (nWidth, nHeight) ;
img.Stretch_Smooth (nWidth, nHeight) ;

// Use SinglePixelProcessProc interface to process image.
// There are over 100 pre-implemented effects;
// please refer to class derived from FCInterface_PixelProcess
FCPixelRotate   aCmd (37) ;
img.SinglePixelProcessProc (aCmd) ;

FCPixelBrightness   aCmd (150) ;    // 150%
img.SinglePixelProcessProc (aCmd) ;

FCPixelMosaic   aCmd(5) ;
img.SinglePixelProcessProc (aCmd) ;

FCPixelOilPaint   aCmd (3) ;
img.SinglePixelProcessProc (aCmd) ;

... custom image processing

// our processor : change pixel's RGB value
class CMyPixelProcessor : public FCSinglePixelProcessBase
{
public:
CMyPixelProcessor (int nR, int nG, int nB) : m_R(nR), m_G(nG),
m_B(nB) {}
private:
virtualvoid ProcessPixel (FCObjImage* pImg, int x, int y,
BYTE* pPixel)
{
PCL_B(pPixel) = FClamp0255 (PCL_B(pPixel) + m_B) ;
PCL_G(pPixel) = FClamp0255 (PCL_G(pPixel) + m_G) ;
PCL_R(pPixel) = FClamp0255 (PCL_R(pPixel) + m_R) ;
}
intm_R, m_G, m_B ;
};

// this class has the same function to upper class,
// but implements other class
class CMyImageProcessor : public FCPixelWholeImageBase
{
public:
CMyPixelProcessor (int nR, int nG, int nB) : m_R(nR), m_G(nG),
m_B(nB) {}
private:
virtualvoid ProcessWholeImage (FCObjImage* pImg,
FCObjProgress* pProgress)
{
for (int y=0 ; y < pImg->Height() ; y++)
{
for (int x=0 ; x < pImg->Width() ; x++)
{
BYTE   * p = pImg->GetBits(x,y) ;
PCL_B(p)   = FClamp0255 (PCL_B(p) + m_B) ;
PCL_G(p)   = FClamp0255 (PCL_G(p) + m_G) ;
PCL_R(p)   = FClamp0255 (PCL_R(p) + m_R) ;
}
if (pProgress)
pProgress->SetProgress (100 * y / pImg->Height()) ;
}
}
intm_R, m_G, m_B ;
};

// use our custom processor
FCObjImage   img ;
img.Load ("test.jpg") ;

CMyPixelProcessor   aCmd (20, 20, 20) ;
img.SinglePixelProcessProc (aCmd) ;

CMyImageProcessor   aCmd (20, 20, 20) ;
img.SinglePixelProcessProc (aCmd) ;

... add text on image

FCObjImage   img ;
img.Load ("c://test.jpg") ;

// now we create text layer
FCObjTextLayer   imgT ;
PACK_TextLayer   tp ;
tp.m_bAddShadow   = false ;
tp.m_bAntiAliased = true ;
tp.m_bBold        = true ;
tp.m_bItalic      = true ;
tp.m_crFont       = PCL_RGBA(0,0,255) ;
tp.m_nFontSize    = 128 ;
tp.m_strFace      = "Arial" ;
tp.m_strText      = "Hello" ;
FCWin32::CreateTextLayer_GDIPlus (imgT, tp) ;

// Now we have created a text image. Additionaly,
// we can add some affect to it, such as gradient color
POINT                 pt1={0,0}, pt2={0,50} ;
FCPixelGradientLine   aCmd (pt1, pt2, PCL_RGBA(0,0,255),
FCColor::crWhite()) ;
imgT.SinglePixelProcessProc (aCmd) ;

// blend text layer on image
RECT   rc = {0, 0, imgT.Width(), imgT.Height()} ;
img.AlphaBlend (imgT, rc, rc, 100) ;

GIMP

The GIMP是一个免费的、分布式的图片润饰、图象制作和处理软件,内含几乎所有图象处理所需的功能,号称Linux下的PhotoShop。GIMP在Linux系统推出时就风靡了许多绘图爱好者的喜爱,它的接口相当轻巧,但其功能却不输于专业的绘图软件;它提供了各种的影像处理工具、滤镜,还有许多的组件模块,对于要制作一个又酷又炫的网页按钮或网站Logo来说是一个非常方便好用的绘图软件,因为它也提供了许多的组件模块,你只要稍加修改一下,便可制作出一个属于你的网页按钮或网站Logo。 如今推出了For Windows版本,还不赶快试试... 注:需要安装GTK+环境包才能正常安装.

下载地址:http://dlc2.pconline.com.cn/filedown.jsp?dlid=10351&linkid=6361451

CxImage

CxImage类库是一个优秀的图像操作类库。它可以快捷地存取、显示、转换各种图像。有的读者可能说,有那么多优秀的图形库,如OpenIL,FreeImage,PaintLib等等,它们可谓是功能强大,齐全,没必要用其它的类库。但我要说,这些类库基本上没有免费的,使用这些类库,你要被这样那样的许可协议所束缚。在这点上,CxImage类库是完全免费的。另外,在使用上述类库时,你会遇到重重麻烦。因为它们大部分是平台无关的,且用C语言写成,有的还夹杂着基本的C++ wrapper和成堆德编译选项的声明需要你去处理。而CxImage类库在这方面做得很好。还有让我最看好的,就是作者完全公开了源代码。相对于那些封装好的图形库和GDI+来说,这一点使我们可以进一步学习各种编解码技术,而不再浮于各种技术的表面。如果想下载CxImage,可以http://www.codeproject.com上下载。 CxImage类库的结构:

一个CxImage对象是一个扩展了的位图。作者只是在位图结构上添加了一些起存储信息作用的成员变量。一个CxImage对象(同时)也是一组层。每个层只有在需要时才会分配相应的缓冲区。CxImage::pDib代表着背景图像,CxImage::pAlpha代表着透明层,CxImage::pSelection代表着被选中的层,被用来创建图像处理时让用户感兴趣的区域。在这三个特殊层面的基础上,你可以增加一些额外的层,这些层可以存储在CxImage::pLayers中。一般说来,层是一个完整的CxImage对象。因此,你可以构造很复杂的嵌套层。下面是CxImage的一些成员变量:

class CxImage
{
...
protected:
void* pDib;            //包含文件头,调色板等等
BITMAPINFOHEADER head; //标准的文件头(位图)
CXIMAGEINFO info;      //扩展了的信息
BYTE* pSelection;      //用户选中的区域
BYTE* pAlpha;          //alpha通道
CxImage** pLayers;     //通用层
}
typedef struct tagCxImageInfo {
DWORD   dwEffWidth;       //DWORD 扫描线宽
BYTE*   pImage;           //图像位数
void*   pGhost;           //if this is a ghost, pGhost point to the body
DWORD   dwType;           //原图像的格式
char    szLastError[256]; //出错信息
long    nProgress;        //监视循环的次数
long    nEscape;          //跳出标志
long    nBkgndIndex;      //GIF, PNG, MNG格式使用
RGBQUAD nBkgndColor;      //RGB三原色透明度
BYTE    nQuality;         //JPEG格式使用
long    nFrame;           //TIF, GIF, MNG使用 :实际的帧数
long    nNumFrames;       //TIF, GIF, MNG使用 :帧总数
DWORD   dwFrameDelay;     //GIF, MNG使用
long    xDPI;             //水平分辨率
long    yDPI;             //垂直分辨率
RECT    rSelectionBox;    //选中的矩形区
BYTE    nAlphaMax;        //阴影的最大不透明度
bool    bAlphaPaletteEnabled;  //如果调色板中有Alpha通道则为真
bool    bEnabled;         //打开绘图函数
long    xOffset;
long    yOffset;
DWORD   dwEncodeOption;   //一些编码选项
RGBQUAD last_c;           //一些优化选项
BYTE    last_c_index;
bool    last_c_isvalid;
long    nNumLayers;
DWORD   dwFlags;
} CXIMAGEINFO;
要在picture box中显示一个png格式的文件,只需:
CxImage image("myfile.png", CXIMAGE_FORMAT_PNG);
HBITMAP m_bitmap = image.MakeBitmap(m_picture.GetDC()->m_hDC);
m_picture.SetBitmap(m_bitmap);
其它格式则类推。
Examples: how to ...
... convert from a format to another
CxImage  image;
// bmp -> jpg
image.Load("image.bmp", CXIMAGE_FORMAT_BMP);
if (image.IsValid()){
if(!image.IsGrayScale()) image.IncreaseBpp(24);
image.SetJpegQuality(99);
image.Save("image.jpg",CXIMAGE_FORMAT_JPG);
}
// png -> tif
image.Load("image.png", CXIMAGE_FORMAT_PNG);
if (image.IsValid()){
image.Save("image.tif",CXIMAGE_FORMAT_TIF);
}
... load an image resource
//Load the resource IDR_PNG1 from the PNG resource type
CxImage* newImage = new CxImage();
newImage->LoadResource(FindResource(NULL,MAKEINTRESOURCE(IDR_PNG1),
"PNG"),CXIMAGE_FORMAT_PNG);
or//Load the resource IDR_JPG1 from DLL
CxImage* newImage = new CxImage();
HINSTANCE hdll=LoadLibrary("imagelib.dll");
if (hdll){
HRSRC hres=FindResource(hdll,MAKEINTRESOURCE(IDR_JPG1),"JPG");
newImage->LoadResource(hres,CXIMAGE_FORMAT_JPG,hdll);
FreeLibrary(hdll);
}
or//Load a bitmap resource;
HBITMAP bitmap = ::LoadBitmap(AfxGetInstanceHandle(),
MAKEINTRESOURCE(IDB_BITMAP1)));
CxImage *newImage = new CxImage();
newImage->CreateFromHBITMAP(bitmap);
... decode an image from memory
CxImage image((BYTE*)buffer,size,image_type);
orCxMemFile memfile((BYTE*)buffer,size);
CxImage image(&memfile,image_type);
orCxMemFile memfile((BYTE*)buffer,size);
CxImage* image = new CxImage();
image->Decode(&memfile,type);
... encode an image in memory
long size=0;
BYTE* buffer=0;
image.Encode(buffer,size,image_type);
...
free(buffer);
orCxMemFile memfile;
memfile.Open();
image.Encode(&memfile,image_type);
BYTE* buffer = memfile.GetBuffer();
long size = memfile.Size();
...
free(buffer);
... create a multipage TIFF
CxImage *pimage[3];
pimage[0]=&image1;
pimage[1]=&image2;
pimage[2]=&image3;
FILE* hFile;
hFile = fopen("multipage.tif","w+b");
CxImageTIF multiimage;
multiimage.Encode(hFile,pimage,3);
fclose(hFile);
orFILE* hFile;
hFile = fopen("c://multi.tif","w+b");
CxImageTIF image;
image.Load("c://1.tif",CXIMAGE_FORMAT_TIF);
image.Encode(hFile,true);
image.Load("c://2.bmp",CXIMAGE_FORMAT_BMP);
image.Encode(hFile,true);
image.Load("c://3.png",CXIMAGE_FORMAT_PNG);
image.Encode(hFile);
fclose(hFile);
... copy/paste an image
//copy
HANDLE hDIB = image->CopyToHandle();
if (::OpenClipboard(AfxGetApp()->m_pMainWnd->GetSafeHwnd())) {
if(::EmptyClipboard()) {
if (::SetClipboardData(CF_DIB,hDIB) == NULL ) {
AfxMessageBox( "Unable to set Clipboard data" );
}    }    }
CloseClipboard();
//paste
HANDLE hBitmap=NULL;
CxImage *newima = new CxImage();
if (OpenClipboard()) hBitmap=GetClipboardData(CF_DIB);
if (hBitmap) newima->CreateFromHANDLE(hBitmap);
CloseClipboard();

需要大家注意的是:整个CxImage类库非常大。如果你只需要能处理其中的几种格式,你可以在主要的头文件ximage.h中找到一些开关选项来关闭一些图像库。JPG、PNG、TIFF中的每一个库,都会向最终程序增加约100KB的内容。而CxImage类库压缩后只有约60KB。所以,你需要谨慎挑选一些你真正需要的类库。作者提供的示例工程在编译后,你会发现如下一些文件: ·CxImage : cximage.lib - static library ·CxImageCrtDll : cximagecrt.dll - DLL not using mfc ·CxImageMfcDll : cximage.dll - DLL using mfc ·Demo : demo.exe - program linked with cximage.lib and the C libraries ·DemoDll : demodll.exe - program linked with cximagecrt.dll ·j2k,jasper,jbig,jpeg,png,tiff,zlib : static C libraries 构建这些工程需要耗费几分钟的时间(中间文件可达60MB)。下面则是使用CxImage类库前必须设置的一些参数:

Project Settings
|- C/C++
|   |- Code Generation
|   |   |- Use run-time library : Multithreaded DLL (must be the same for
|   |   |  all the linked libraries)
|   |   |- Struct member alignment : must be the same for all the linked
|   |   |  libraries
|   |- Precompiled headers : not using precompiled headers
|   |- Preprocessor
|       |- Additional Include Directories:  ../cximage
|- Link
|- General
|- Object/library modules: ../png/Debug/png.lib
../jpeg/Debug/jpeg.lib
../zlib/Debug/zlib.lib
../tiff/Debug/tiff.lib
../cximage/Debug/cximage.lib  ..
2020-03-09 10:53:00 qq_41911570 阅读数 65
  • Linux系统目录及必备命令学习

    通过本课程的学习,大家可以从懵懂到熟悉,从熟悉到熟练,能够在企业中熟练运用,同时在学习的过程中有任何不明白的地方,都可以向我咨询,我会积极帮助大家解决问题。

    217人学习 吴光科
    免费试看


看看前言也许会让你对数字图像处理更感兴趣

第一章 品位像素

在过去的很长一段时间里,只有少数能够接触到昂贵设备的专业人员才有机会使用计算机操作数字图像(即数字图像处理)。这种专业人员与设备的组合通常只会出现在一些研究性实验室,因此数字图像处理技术起源于学术领域。如今,台式计算机的处理能力已日益增强,而且几乎每个人都拥有一些获取数字图像的设备,例如手机摄像头、数码相机、扫描仪。这些设备产生了大量的数字图像,使得数字图像处理变得和文字处理一样普及。

如今,IT界的专业人员不能局限于简单地熟悉数字图像处理过程,而是要能够有理论深度地操作图像及相关的数字媒体。在医学和媒体以及其他所有领域的工作中,这已经成为工作流程中愈来愈重要的一环。同样,软件工程师和计算机科学家在开发程序、数据库以及相关系统时也越来越多地面临需要正确处理数字图像的情况。对图像处理实际经验的缺乏,加之对基础理论理解的模糊,以及对问题难度的低估,常常使我们的解决方案效率低下,甚至出现严重错误致使我们对图像处理失去信心。

1.1 图像编程

尽管术语“图像处理”和“图像编辑”经常可以交换使用,我们还是要引入下面更加精确的定义。数字图像编辑是指用已有的软件(如Photoshop、Corel Paint)来操作数字图像;而数字图像处理是指数字图像处理程序的概念、设计、开发以及增强。
现代编程环境以及它们所提供的广泛的API(应用程序接口)使非专用人员可以轻松获得几乎所有方面的计算处理功能,例如网络、数据库、图形、声音或者成像。开发一款能够深入图像、处理其内部独立元素的程序是很吸引人的。你会发现在正确的理论知识指导下,一幅图像最终会变为一个简单的数值矩阵;利用合适的工具,你可以对它做任何想要的操作。
相对于数字图像处理,计算机图形学致力于从诸如三维模型之类的几何描述中合成数字图像[31,37,103]。尽管图形学专家感兴趣的工作时虚拟现实场景,特别是电脑游戏中的快速渲染,该领域仍使用了大量源自图像处理的方法,比如图像变换(变形)、从图像数据完成三维模型重建以及基于图像的非真实渲染等等专业技术[77,104]。相似的,图像处理也利用了许多源自于计算几何和计算机图形学的思想,例如医学图像处理中的体模型。这两个领域可能在进行电影或视频的后期数字制作、创建特效的时候最为接近[105]。本书将全面介绍图像和图像序列(即视频)的有效处理方法。

1.2 图像分析和计算机视觉

第二章 数字图像

数学图像是本书的核心主题,与多年前不同,数学图像这个术语在当令已被普遍使用,因此我们没有必要再去深入地解释它。然而,本书并非针对所有数字图像类型,而是将注意力集中在那些由排列在规则矩形网络上的图像元素(通常被称为像素)构成的图像上。

2.1 数字图像的类型

人们每天都会接触到各种各样的数字光栅图像,例如人物和风景的彩色照片、打印文档的灰度扫描、建筑平面图、传真的文档、屏幕截图、诸如X射线和超声波之类的医学图像等(图2.1)。不论这些图像源自哪里,它们最终会以图像元素构成的矩形有序阵列的形式呈现。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 图像获取

把真实场景变为数字图像的过程是复杂多样的,而且在大多数情况下你所处理的图像已经是数字格式的,因此在这里我们只略述这个过程中的核心步骤。因为大多数图像获取方法在本质上都是基于典型光学照相机的变种,所以我们将从详细考察光学照相机的成像过程开始。

2.2.1 针孔照相机模型

从13世纪起,针孔照相机作为最简单的照相机模型被广泛使用,那时它被称为“奥布斯古拉照相机”。尽管现在除了一些摄影爱好者以外已经没有人再用它了,但是针孔照相机模型对于理解简单照相机的核心光学组件仍然是十分有用的。
针孔照相机由一个密闭的盒子组成,在前侧面板上有一个小孔,光线透过这个孔在后侧面板上形成场景的缩小反转的像(图2.2)。

透视变换

针孔照相机的几何性质十分简单,光轴穿过针孔垂直交于图像平面。我们假设一个可见目标(图2.2中的仙人掌)位于与针孔水平距离为Z、与光轴垂直距离为Y的位置,它的投影的高度y取决于两个参数:照相机盒子的固定深度f和坐标系原点与目标之间的距离Z。通过比较,
在这里插入图片描述
在这里插入图片描述
随着像的比例而变化,且和盒子的深度即f成正比,这和日常照相机焦距变化相似。对于一幅固定尺寸的图像,小的f(即短焦距)会产生较小的像和较大的视角,就像使用了广角镜头一样;而增加焦距f会造成较大的像和较小的视角,正如使用了远距镜头的效果。公式(2.1)中的负号代表投影图像在水平方向和垂直方向经过了反转并且旋转了180度。公式(2.1)描述了今天普遍认可的投影变换关系,这个理论模型的两个重要性质是3D空间中的直线总是投影为2D直线,而3D空间中的圆投影为椭圆。

2.2.2 “薄”透镜

实际上,玻璃透镜或者光学透镜系统被用来代替针孔,它们具有多方面的优越性但同时也复杂得多。用图2.3中所示“薄透镜”代替针孔,它们具有多方面的优越性但同时也复杂得多。

2.2.3 数字化

投影在照相机像平面上的像,本质上是一种二维的、时间相关的、连续分布的光能量。
在这里插入图片描述
为了把这种像转换成计算机上的数字图像,需要经过以下三个主要步骤。
1.连接的光线分布必须被空间采样。
2.步骤1的采样结果必须在时间域被采样,以创建一幅图像。
3.最后,采样的值必须被量化到有限范围的整数,这样它们才能在计算机中被表示。

步骤1:空间采样

像的空间采样(即从连续信号到其离散形式的转换)依赖于图像获取设备(例如数码相机和摄像机)传感器单元的几何特征。各个传感器单元被排列在传感器平面(图2.4)上有序的行中,彼此之间几乎总是保持着合适的夹角。另外,可以在某些特殊的产品中发现一些具有六边形单元和圆形传感器结构的成像传感器。
在这里插入图片描述

步骤2:时域采样

时域采样是通过在均匀的时间间隔上测量每个独立传感器单元上的光入射量来完成的。数码相机中的CCD通过触发充电过程,然后测量指定时间内在CCD接受光照所积累的电荷数量来完成时域采样。

步骤3:量化像素值

为了让计算机上存储和处理图像中的数值,它们通常被转换为整数值(例如256=28或者4096=212)。在一些专业应用中(例如医学图像处理)偶尔会出现需要用到浮点型的情况。转换是通过使用一个模数转换器进行的,这个转换器通常被直接集成在传感器单元中(这样转换就发生在图像拍摄的过程中)或者通过专门接口硬件执行。
把图像看作离散函数
这三步的操作结果是以二维有序整数矩阵(图2.5)的形式对图像的描述。形式化表述为:一幅数字图像 I 是一个将整数坐标N * N映射到某个范围的图像值P的二维函数,使得
在这里插入图片描述
现在我们已经做好了把图像转移到计算机中的准备,因此图像可以被保存、压缩或者处理成我们所选择的文件格式。这时,图像源自何处对我们来说已经不重要了,因为现在它就是一个简单的二维数值数据矩阵。在继续讲解以前,我们需要了解一些更重要的定义。
在这里插入图片描述

2.2.4 图像尺寸和分辨率

后面我们将假设图像是矩形的,尽管这是一个相对安全的假设,但仍然存在例外。一幅图像的尺寸可以直接由图像矩阵 I 的宽度M(列数)和高度N(行数)确定。
一幅图像的分辨率指定了它在真实世界中的空间尺寸,以每个计量单位的图像元素数给出,例如印刷产品中使用的点每英寸(dpi)或者线每英寸(lpi),又如卫星图像中的像素每千米。在大多数情况下,图像分辨率在水平方向和垂直方向上是相同的,因为图像元素是方的。
除了一些处理几何运算的算法以外,一般没有必要了解一幅图像的空间分辨率。然而,在涉及几何元素的情况下精确的分辨率信息就变得非常重要了,例如要在图像上绘制圆或者测量图像中的某段距离。正因为此,大部分的图像格式和专业用途设计的软件系统都包含精确的分辨率信息。

2.2.5 图像坐标系统

为了确定图像上哪个位置对应哪个像素,我们需要引入一个坐标系。与数学上惯用的坐标系相反,图像处理中的坐标系在垂直方向经过了反转;也就是说y坐标自顶向下逐渐增大,原点位于左上角(图2.6)。尽管这种坐标系不论在理论上还是实际上都没有什么优越性,事实上它还使得几何变换描述起来更加困难,但这种坐标系几乎无一例外地在图像处理软件系统中被使用。此系统起源于电视电子学,图像中的各行随着电子束按照从屏幕顶部到底部的顺序被显示。我们让行和列的编号从0开始,因为Java中数组的下标是从0开始的。
在这里插入图片描述

2.2.6 像素值

一个图像元素中所含的信息取决于用来表示它的数据类型。像素值实际上总是长度为k的二进制字,因此一个像素可以表示2k种不同的值。k值由图像的位深度(或深度)决定,通常是处理器的字长。
一个单独像素的位数取决于图像的种类,例如二值图、灰度图、或者RGB彩色图。表2.1总结了常用图像类型。
在这里插入图片描述
在这里插入图片描述

灰度图像(强度图像)

一幅灰度图像的图像数据由代表图像强度、亮度或密度的单个通道构成。在大多数情况下,只有正的值才有意义,因为数字代表了光能量的强度,而这个强度不可能是负的,因此通常使用[0…2k-1]中的整数值。例如,一幅典型的灰度图像每个像素使用k=8位(1字节),即强度值在范围[0…255]中,其中0代表最小亮度(黑色),255代表最大亮度(白色)。
在专业的摄影、印刷以及医学和天文学中,一个像素8位并不够用,在这些领域中经常会遇到12、14甚至16位的图像深度。注意位深度是指用于表示单个颜色的位数,不是表示整个像素所需的位数。例如,一幅8位深度的RGB编码彩色图像每个通道需要8位,总共需要24位,而具有12位深度的同样的图像总共需要36位。

二值图像

彩色图像

特殊图像

第三章 ImageJ

直到几年前,从事图像处理的还只是那些能够接触昂贵的商业图像处理工具或者需要自己开发软件包的一小群人。通常这些自制环境都是从一些小的软件组件开始的,例如,从磁盘上载入图像或者将图像保存至磁盘的程序。开发这些小软件并不总是很容易,因为它需要处理一些蹩脚的甚至私有的文件格式。最常用的解决方法是:简单定义一种新的图像文件格式,针对特殊领域、特殊应用、甚至一个单独的工程,这种格式经常进行了优化。这种解决方法直接导致多种不同的文件格式的产生,大多数文件格式在今天已经不再被使用甚至已经被遗忘[71,74]。然而,在20世纪80年代和90年代初期,编写这些不同格式间转换的软件却是一项重要的工作,占用了大量的人力。早期,即使在计算机屏幕上显示图像也是很难的,因为操作系统、API和显示硬件只能提供一些边缘的支持,而用普通的硬件将图像或视频捕获到计算机中也是近乎不可能的。因此,为了在计算机上做一些初级的图像操作或者进行一些高级的图像处理,人民往往需要花费几个星期甚至几个月来做前期的准备工作。

幸运的是,如今的情况以及大为好转,只有一些通用的图像文件格式仍然存在(参考第2.3节),这些图像格式可以被许多现有的工具和软件库方便地处理。大部分为C/C++、Java和其他流行的编程语言设计的标准API,已经至少可提供一些对图像和其他媒体数据操作的基本支持。尽管在这个层次下还有许多开发工作要做,但它已经使我们的工作变得更加简单,由此我们可以把注意力集中于数字图像处理中那些更有趣的方面。

3.1 图像操作和处理

传统上,数字图像处理软件的目标可以是图像的操作或处理,其面向的用户也具有不同需求:既有从业者和设计人员,也有软件编程人员。图像操作软件包(例如Adobe Photoshop , Corel Paint等)通常提供了方便的用户接口、大量易于使用的函数以及图像进行相关交互式工作的工具,有时甚至可以通过编写脚本或者添加自编程组件 来扩展其标准功能。

相对于上面的一类工具来说,数字图像处理软件主要是针对软件开发者、科学家以及从事图像相关工作的工程师而设计的。这些软件开发的重点不在于软件本身的交互性和易用性,而是提供广泛的、成熟的软件库来简化新的图像处理算法、原型和应用的实现过程。在众多此类工具中,比较流行的有Khoros/VisiQuest、IDL、Matlab和ImageMagick。这类系统不仅支持C/C++,还提供专门的脚本语言或可视化的编程辅助工具。

实际上,图像操作和图像处理紧密相关。例如,尽管Photoshop的目标是帮助非程序设计人员进行图像操作,但软件本身却实现了许多传统的图像处理算法。同样,许多Web应用程序(例如那些基于ImageMagick的程序)也是使用看服务器端的图像处理算法。因此,图像处理是任何图像软件的基础,并不是一个完全不同的类别。

3.2 ImageJ综述

ImageJ是本书所采用的软件,它是以上讨论的两类工具的结合。它提供了一套现成的工具,用于图像的查看和交互操作,同时也可以通过应用某种“真正的”编程语言编写新的软件组件来得到扩展。ImageJ完全采用Java语言编写,因此它与平台无关,可以不加修改地运行在Windows、MasOS或者Linux操作系统上。Java的动态执行模型允许将新的模块(“插件”)作为独立的Java代码段来编写,这些代码甚至不需要重新启动ImageJ就可以被编码、加载和在运行的系统中执行,这使得ImageJ成为开发和测试新的图像处理技术和算法的理想平台。由于Java语言作为许多工程课程的首选语言已经非常流行,所以通常学生不需要花费大量的时间去学习另一种编程语言就能够非常轻松地使用ImageJ。同时,ImageJ可免费获取,不论学生、教师还是从业人员都可以合法地在任何计算机上安装使用它,而不必购买许可。因此,ImageJ是一个进行数字图像处理教学和自我训练的理想平台,同时它也在全球范围内的许多实验室里被用于正式的研究和应用程序开发,特别是在生物学和医学的图像处理中。

ImageJ是由美国国家卫生研究院(NIH)的Wayne Rasband[79]开发的,起初是作为它的前身(只能在Apple Macintosh平台下使用的)NIH-Image的替代品。ImageJ的当前版本、更新、文档、全部源代码、测试图像以及不断增加的第三方插件集都可以在ImageJ的网站下载。软件的安装很简单,具体的操作指南可以从网站、Werner Bailer的编程教程[4]以及本书的附录C中获得。

尽管ImageJ是一款优秀的工具,但由于其起源和历史,使得它并不是十全十美的。从软件工程的角度来看,它的架构设计并不直观,我们期望它具有更强的灵活性(例如,若干任务可以通过多种不同的方式来完成)。出于结构化考虑,附录C按照任务的不同领域进行了分类,并精选了一些关键功能;一些很少使用的特殊功能被省略了,当然它们可以在ImageJ的文档和(在线)源代码中找到。

3.2.1 关键特征

作为一个纯粹的Java应用程序,ImageJ应该能运行在任务安装了Java运行时环境(JRE)的计算机上。事实上ImageJ包含了自己的Java运行时,因此无需在计算机上再单独安装Java。大多数情况下,ImageJ被当作一个独立的应用程序来使用,但是它也可以作为Java小应用程序(applet)在Web浏览器中运行。有时它还会在服务器端基于Java的Web应用程序中被使用(细节参考[4])。总的来说,imageJ的主要特征包括如下:

  • 一系列交互式即用工具,用于创建、显示、编辑、处理、分析、加载和存储图像,且支持多种常见文件格式,同时支持16位整型、32位浮点型的图像和图像序列。
  • 一种简单的插件机制,通过编写Java代码段(通常较小)来扩展ImageJ的核心功能。本书中所有的代码示例都是基于这种插件的。
  • 一种宏语言以及相应的解释器,可以简单地通过合并已有的函数来实现较大的处理模块,不需要任何Java的知识。本书中没有使用宏,但可以在ImageJ的在线文档中找到宏的详细说明。

3.2.2 交互式工具

ImageJ启动后首先打开的是主窗口(图3.1),其中包括下列菜单项。

  • File: 打开、保存和创建新的图像;
  • Edit: 编辑和绘制图像;
  • Image: 修改和转换图像、几何操作;
  • Process: 图像处理,包括点运算、滤波器以及多幅图像间的算法操作;
  • Analyze: 对图像数据进行统计测量、直方图和特殊显示格式;
  • Plugin: 编辑、编译、执行和管理用户定义的插件。
  • 当前版本的ImageJ可以打开多种常见格式的图像,包括TIFF(仅支持未压缩的)、JPEG、GIF、PNG和BMP,同样也支持医学和天文图像处理中常用的DICOM和FITS格式。与大多数图像编辑程序相同,所有的交互式操作都被应用于当前操作的图像,即由用户选中的图像。ImageJ还提供了一种简单的(单步)“撤销”机制,可以从用户定义的插件的操作结果中恢复。
    在这里插入图片描述

3.2.3 Image插件

插件是采用简单的标准化接口来扩展ImageJ功能的小型Java模块(图3.2)。你可以通过ImageJ主菜单中的Plugin菜单(图3.1)被创建、编辑、编译、调用以及组织。插件可以通过分组来提高其模块化,而插件命令可以任意地放置在主菜单结构中。另外,ImageJ许多内置的功能实际上也是通过插件来实现的。
在这里插入图片描述
从技术上来讲,插件是实现了由ImageJ定义的特殊接口规范的Java类,包括
两种不同类型的插件:

  • PlugIn: 启动插件时不需要打开一幅图像;
  • PlugInFilter:启动插件时当前操作图像被传递给插件;
    在本书的所有示例中,我们几乎全部采用第二种类型(PlugInFilter)来实现图像处理操作。接口规范要求所有PlugInFilter类型的插件必须至少实现两种方法:setup()和run(),以下是方法的原型:
int setup(String arg,ImagePlus im)

当插件启动时,ImageJ首先调用这个方法来验证该插件的功能是否与目标图像匹配。setup()以32位整型值的形式返回一个二进制标志位向量来描述插件的属性。

void run(ImageProcessor ip)

这个方法执行插件的实际功能。它接受一个单独的参数ip,这个参数是ImageProcessor类的对象,即包括待处理的图像和相关的信息。run()方法没有返回值(void),但可能会修改传入的图像或者创建新的图像。

3.2.4 第一个例子:图像取反

让我们用一个实际的例子来快速阐明这种机制。我们第一个插件的任务是对一幅8位灰度图像取反,使其由正片变为负片。后面我们将会看到,对图像的灰度值取反是一种典型的点运算(点运算的内容将在第5章详细讨论)。在ImageJ中,8位灰度图像的像素值取值范围是0(黑色)~255(白色),此外我们假设图像的宽度和高度分别是M和N。取反操作十分简单:将每个像素的灰度值I(u,v)替换为它的相反值,
在这里插入图片描述
其中(u,v)是图像坐标,u=0…M-1,v=0…N-1 。

插件类:My_Inverter

把我们的第一个插件命名为“My_Inverter”,它既是Java类的名字,也是包含它的源文件的名字(程序3.1)。名字中的下划线“_”使这个类识别为一个插件,并且在启动时自动把它插入菜单列表。文件My_Inverter.java中的Java源代码包含一下import声明,紧接着实现了PlugInFilter接口(因为它将要应用于一幅已存图像)的类My_Inverter的定义。

setup()方法

当一个PlugInFilter类型的插件被执行时,ImageJ首先调用它的setup()方法来获得关于插件本身的信息。在这个例子中,setup()只返回值DOES_8G(由PlugInFilter接口指定的静态整型常量),表明这个插件可以处理8位灰度图像(程序3.1 第8行)。setup()方法的参数arg和im在这个例子中没有被使用(参考联系3.4)。

package com.myplugin;

import ij.ImagePlus;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;

public class My_Inverter implements PlugInFilter{

	@Override
	public void run(ImageProcessor ip) {
		int w = ip.getWidth();
		int h = ip.getHeight();
		
		//在图像所有坐标中循环
		for (int u = 0; u < w; u++) {
			for (int v = 0; v < h; v++) {
				int p = ip.getPixel(u, v);
				ip.putPixel(u, v, 255-p);
			}
			
		}
	}

	@Override
	public int setup(String arg0, ImagePlus arg1) {
		return DOES_8G;
	}
}

run()方法

如上所述,一个PlugInFilter插件的run()方法接受一个ImageProcessor类型的对象(ip),其中包含待处理的图像及其所有相关信息。首先,我们用ImageProcessor类的方法getWidth()和getHeight()来获得ip所引用图像的尺寸,接着我们用两个嵌套的for循环(循环变量u、v分别代表水平和垂直坐标)遍历图像的所有像素。为了读取和写入像素值,我们用到了ImageProcessor类的另外两个方法:

int getPixel(int u,int v)

返回(u,v)处的像素值,如果(u,v)超过图像边界则返回0。

void putPixel(int u,int v,int a)

设置(u,v)处的像素值为一个新值a,如果(u,v)超过图像边界则不做任何操作。
关于这些方法和其他方法的细节可以在附录C中的ImageJ参考文献中找到。
若确定没有图像边界以外的坐标被访问(像程序3.1中My_Inverter那样),同时能够保证插入的像素值不超过图像处理算法的范围,那么我们就可以用较快的方法get()和set()来分别代替getPixel()和putPixel()方法(见附录C)。处理图像最有效的方法是彻底避免使用读/写方法,而直接访问相应像素数组的元素,参见附录C.7.6。

编辑、编译和执行插件

我们的插件的源代码应该被保存在文件My_Inverter.java中,这个文件位于<i,j>/plugins/或者一个一级子目录中,新的插件文件可以通过ImageJ的Plugins——>New菜单创建。ImageJ还提供了一个用于编写插件的内置Java编辑器,可以通过Plugins——>Edit…菜单来使用它,可惜的是它对较正式的编程没有多大帮助。一个比较好的选择是使用现代的编辑器或者专业的Java编程环境,例如Eclipse、NetBeans或者JBuilder,这些软件均可免费获取。

为了编译插件(到Java字节码),ImageJ附带了自己的Java编译器作为运行时环境的一部分。编译和执行新的插件,可以简单地使用菜单:
在这里插入图片描述
编译错误将会显示在独立的日志窗口中。一旦插件被编译,相应的.class文件就会被自动加载,该插件则被应用于当前操作图像。如果当前没有打开的图像或者当前没有打开的图像或者当前图像不能被这个插件处理,则显示一条错误消息。

启动时,ImageJ会自动地加载所有在<i,j>/plugins/目录(或一级子目录)下找到的正确命名的插件,并把它们装入Plugins菜单。这些插件无需重新编译就可以立即执行。对插件的引用也可以通过命令
在这里插入图片描述
手工置于ImageJ菜单树的任何其他位置。一系列插件的调用和其他ImageJ命令可以通过Plugins——>Macros——>Record记录为宏程序。

显示和“撤销”结果

程序3.1中,我们的第一个插件并没有创建新的图像,而是“破坏性的”修改了目标图像。情况并不总是这样,插件也可以另外创建图像或者只是进行统计计算,而不对原始图像进行任何修改。我们的插件并不包含任何显示修改后图像的命令,这也许有些奇怪。实际上当传递给插件的图像认为被修改时,ImageJ会自动地完成该图像的显示。另外,ImageJ在将图像传递给PluginFilter类型的run()方法以前会自动保存一个副本(“快照”),这个功能使得用插件处理过图像之后还可以重新恢复原始图像(Edit——>Undo菜单),而不必在插件代码中加入任务显式的预防措施。

第四章 直方图

、

4.1 什么是直方图

直方图是一种频率分布图,它描述了不同强度值在图像中出现的频率。这个概念可以通过图4.2所示的灰度图像来解释,一幅 图像I的灰度值范围为:
在这里插入图片描述
它的直方图h中正好包含K个条目(对于一幅典型的8位灰度图像,K=28=256),每一个单独的条目被定义为:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因此,h(0)表示灰度值为0的像素点数目,h(1)表示灰度值为1的像素点数目,依次类推。最后,h(255)表示具有最大灰度值255=K-1的白色像素点数目。灰度直方图运算的结果是一个长度为K的一维向量h。图4.3给出了一个具有K=16种可能灰度值的图像的例子。
在这里插入图片描述
由于直方图并未反映出其每个条目对应于图像中的位置,因此直方图不包含图像中像素点的空间排列信息。这是因为直方图的主要功能是以紧凑的形式反映图像的统计信息(例如强度值的分布)。是否可以仅利用直方图来重建一幅图像呢?也就是说,直方图是否可以以某种方式逆转?在非特殊情况下,答案是否定的。例如,用同样数目的具有特定灰度值的像素点可以建立多种多样的图像,尽管这些图像看上去差别很大,但它们却具有完全相同的直方图(图4.4)。
在这里插入图片描述

4.2 解读直方图

直方图能够描述图像获取过程中产生的问题,例如那些涉及对比度以及动态范围的问题,以及由图像处理过程造成的瑕疵。通过检查直方图分布的范围和均匀程度,可以确定一幅图像是否有效地利用了它的强度范围(图4.5)。
在这里插入图片描述

4.2.1 图像获取

曝光

直方图使典型的曝光 问题变得显而易见。例如,一端较大强度取值范围未使用而另一端充满峰值的直方图(图4.6)就代表图像曝光不合适。
在这里插入图片描述

对比度

对比度包含两方面的含义,一方面是指一幅给定图像中强度值的有效利用范围,另一方面是指图像中最大和最小像素强度值的差距。一幅全对比度图像有效地利用了全部的可用强度值范围:
在这里插入图片描述
利用这个定义,一幅图像的对比度可用轻易地从直方图中得到。图4.7说明了对比度的变化时怎样影响直方图的。
在这里插入图片描述

动态范围

一幅图像的动态范围是指图像中不同像素灰度值的数目。理想情况下,即所有取值都被利用的情况下,动态范围是所有可能像素值K。如果一幅图像的可用对比度范围是:
在这里插入图片描述
那么动态范围的最大可能值将在这个可用对比度范围内所有强度值都被利用(即在图像中出现;图4.8)的情况下达到。

2016-10-07 23:27:19 Real_Myth 阅读数 6138
  • Linux系统目录及必备命令学习

    通过本课程的学习,大家可以从懵懂到熟悉,从熟悉到熟练,能够在企业中熟练运用,同时在学习的过程中有任何不明白的地方,都可以向我咨询,我会积极帮助大家解决问题。

    217人学习 吴光科
    免费试看

from:http://www.wtoutiao.com/p/18a4MqP.html

几种常用的图像处理函数库

OpenCV

OpenCV的全称是:Open Source Computer Vision Library。OpenCV是一个基于(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。

OpenCV用C++语言编写,它的主要接口也是C++语言,但是依然保留了大量的C语言接口。该库也有大量的Python, Java and MATLAB/OCTAVE (版本2.5)的接口。这些语言的API接口函数可以通过在线文档获得。如今也提供对于C#,Ch, Ruby的支持。

所有新的开发和算法都是用C++接口。一个使用CUDA的GPU接口也于2010年9月开始实现。


点评:

功能十分的强大,而且支持目前先进的图像处理技术,体系十分完善,操作手册很详细,手册首先给大家补计算机视觉的知识,几乎涵盖了近10年内的主流算法;然后将图像格式和矩阵运算,然后将各个算法的实现函数。该库显示图像极其方便,但该库似乎不大稳定,对32F和16S、8U的图像数据支持上bug重重。好处是该库是开放的,所以自己可以修改;而且支持CVS。另外该库用的是IPL矩阵库,速度很快。

CxImage

CxImage类库是一个优秀的图像操作类库。它可以快捷地存取、显示、转换各种图像。有的读者可能说,有那么多优秀的图形库,如OpenIL,FreeImage,PaintLib等等。


点评:

该开发包完全开放源代码,图像封装为一个类,功能极为强大,与Windows、MFC支持极好,支持图像的多种操作(线性滤波、中值滤波、直方图操作、旋转缩放、区域选取、阈值处理、膨胀腐蚀、alpha混合等等),支持从文件、内存或者win32api定义的位图图像格式中读取图像,支持将图像显示在任意窗口,功能可谓很强大了,而且对像素的操作很方便,另外还有一个界面很强的demo,可以直接在上面进行二次开发,推荐使用!

缺点:里面的子库很多,用起来可能较麻烦;而且感觉速度稍慢,不如后面提到的freeimage,但功能真的十分强大啊!

CxImage类库是一个优秀的图像操作类库。它可以快捷地存取、显示、转换各种图像。有的读者可能说,有那么多优秀的图形库,如OpenIL,FreeImage,PaintLib等等,它们可谓是功能强大,齐全,没必要用其它的类库。但我要说,这些类库基本上没有免费的,使用这些类库,你要被这样那样的许可协议所束缚。在这点上,CxImage类库是完全免费的。另外,在使用上述类库时,你会遇到重重麻烦。因为它们大部分是平台无关的,且用C语言写成,有的还夹杂着基本的C++ wrapper和成堆德编译选项的声明需要你去处理。而CxImage类库在这方面做得很好。还有让我最看好的,就是作者完全公开了源代码。相对于那些封装好的图形库和GDI+来说,这一点使我们可以进一步学习各种编解码技术,而不再浮于各种技术的表面。

CImg

CImg 库是一个免费、开源的图像处理C++库,名称原意是 Cool Image,正如其名,CImg是一个非常优秀、功能强大、代码简洁、使用方便的C++ 图像处理库。它不仅非常适合科学家、研究生做科研时使用,也适合在工业应用工程开发中使用,更适合的是,对于有志于开发简洁、高效、功能强大的图像处理库的人而言,CImg的源码是不可多得的学习和参考资料。


点评:

就一个.h文件所以用起来很简明,但感觉功能上不如CxImage。可以与CxImage配合使用,因为CImg提供了基于lapack的矩阵运算函数和完善的线性滤波卷积函数,同时CImg做像素运算还是很方便的。另外,独有Display类可以方便的实现各种显示,包括显示图像、打字、画线等等。还有,该库有个基于光流的多尺度图像配准例子,很好。

FreeImage

FreeImage [1] 是一款免费的、开源的、跨平台(Windows 、Linux 和Mac OS X )的,支持20 多种图像类型的(如BMP 、JPEG 、GIF 、PNG 、TIFF 等)图像处理库。其最大优点就是采用插件驱动型架构,具有快速、灵活、简单易用的特点,得到了广泛使用。

FreeImage 的主要功能有多格式位图的读写;方便的位图信息获取;位深度转换;位图页面访问;基本的几何变换和点处理;通道合并与混合等。FreeImage 暂时不支持矢量图形和高级图像处理,位图绘制需要由用户来完成。

FreeImage 中的所有函数都以FreeImage_ 开头,如图像文件的读写函数分别为FreeImage_Load 和FreeImage_Save 。FIBITMAP 数据结构保存着位图信息和像素数据,是FreeImage 的核心。


点评:

C语言的体系,大量使用指针运算速度可以保证,内含先进的多种插值算法。另外独有的支持meta exif信息的读取。该库最大的特点就是比较简练,只把重点放在对各种格式图像的读取写入支持上,没有显示部分,实际编程的时候还是需要调用API函数进行显示。



2016-01-25 15:28:02 u014136513 阅读数 1435
  • Linux系统目录及必备命令学习

    通过本课程的学习,大家可以从懵懂到熟悉,从熟悉到熟练,能够在企业中熟练运用,同时在学习的过程中有任何不明白的地方,都可以向我咨询,我会积极帮助大家解决问题。

    217人学习 吴光科
    免费试看

在过去的几十年里,地理信息在互联网上的使用和展示经过了飞速发展。第一代互联网上展示的地图是嵌入到HTML中的静态图片,例如GIFJPEG或者PNG格式,这是使用互联网展示地图的第一步。今天,我们希望可以在地图上交互,例如地图的放大和缩小;可以打开或者关闭图层以便寻找有用信息。这些功能非常有用,但我们不只期望如此。地图应该能够在桌面、平板电脑、手机甚至汽车中的仪表盘上进行交互式操作。

有趣的是,许多人实际上是工作在地理空间领域的,WebGIS的使用会超过我们日常生活中所做的工作。特别是在遥感领域,虽然图像处理与分析仍旧在传统的桌面端占有相当的分量,但是,这种情况即将改变。最近一些Exelis VIS公司(ENVI原产商)的项目,很多是基于Web的图像处理的未来。这种应用模式的优势非常清晰:

l  基于Web的图像处理允许用户使用分布式数据,即无论数据存放在何处,只要通过网络或者web能够访问即可。数据可能在桌面端、局域网或者世界上某个角落的服务器上。

l  可以使用Esri发布的地理地图或者其他资源来展示数据,可以使用矢量图层。能够使用目录例如Jagwire管理数据。

使用ENVI Services Engine中的功能来进行图像处理与分析。一旦配置好系统,便可以非常容易的构建Web应用程序进行数据显示、处理和共享。快速获取、处理和传播有用信息到终端用户的能力,这种基于Web的图像处理是相对于传统桌面处理与分析是一个巨大的进步。

[转载]基于Web的图像处理

1 Web图像处理应用框架

来看几个基于云的图像处理与分析的例子。第一个例子中的客户端使用JavaScript构建。数据使用的是Esri发布的全球Landsat影像服务,该数据可以被查询和显示在Esri的地理底图上。一旦选择影像,就可以使用ENVI Services Engine中的功能进行处理。在以下的截图中,第一幅图展示的是查询Landsat影像服务,第二幅图展示的是对选择的数据做NDVI处理。当执行图像分析的时候,输出数据存放在服务上,PNG图片将展示在web客户端中Esri的地理底图上。

[转载]基于Web的图像处理

2 获取数据服务

[转载]基于Web的图像处理

3 植被分析

在下面的例子中,使用的不是影像服务而是本地服务器上的数据。访问应用程序需要一个密码。用户可以通过浏览器访问。在该示例中,使用的是Esri JavaScript API来提供地理底图。用户能够浏览可用数据或者从自己电脑上传数据。这些数据就会被添加到数据目录中,其他用户也可以使用。选择数据后,即可通过ENVI Services Engine任务进行图像处理与分析。

[转载]基于Web的图像处理

4 数据目录

[转载]基于Web的图像处理

5 使用ESE进行图像分类

基于Web的图像处理有着很大的应用潜力,使用该项技术进行冰川变化的跟踪、森林火灾的监测、自然灾害的跟踪等。随着可用性数据的快速增长,基于Web的图像处理提出了有效的访问数据和分析数据的快速有效的方式。