opencv学习笔记:直方图(histogram)

一、直方图的理解

  直方图就是对数据的统计图,比如最常使用的灰度图像直方图就是对灰度图像的像素值进行频率统计,按照一定的顺序绘制的图,横坐标是像素值,纵坐标是像素值出现的频率。缺点是会丢失像素的位置信息。
  通常对图像的亮度、梯度、像素值、方向等都可以进行直方图绘制。

二、直方图均衡化----equalizeHist()

2.1 介绍
  直方图均衡化是通过拉伸像素强度分布范围来增强图像对比度的一种方法。
  其本质是扩大了动态范围,因此,原来灰度不同的像素经过处理后可能变得相同,形成一片相同灰度的区域,各个区域之间有明显的边界,从而出现了伪轮廓。
  对于对比度本来就很高的图像,均衡化之后对比度会下降,对于已经均衡化的图像,再次均衡化不会产生什么影响。
2.2 API介绍

void equalizeHist( InputArray src, OutputArray dst );
参数src:8位单通道图像;
参数dst:和原图一样的大小和类型。

2.3 均衡化推导过程
  ①计算输入图像的直方图;
  ②计算直方图中每个灰度级的累积分布概率;
  ③通过查表法进行灰度值变换
  原理公式:
      在这里插入图片描述
  其中 s s s表示输出的像素灰度值, p r ( x ) p_{r}(x) pr(x)表示概率密度函数, r r r表示待处理图像的灰度,取值范围是[0, L − 1 L-1 L1],对于灰度图来说就是[0, 255]。
  直方图均衡化的过程就是对于输入图像的灰度值,使用一个变换函数 T ( r ) T(r) T(r),计算得到新的灰度值,该变换函数满足的条件:
  ① T ( r ) T(r) T(r) [ 0 , L − 1 ] [0, L-1] [0,L1]上严格单调递增;
  ②当 0 < = r < = L − 1 0<=r<=L-1 0<=r<=L1时, 0 < = T ( r ) < = L − 1 0<=T(r)<=L-1 0<=T(r)<=L1
条件①满足了输出灰度值与输入灰度值一一对应的关系;条件②满足了输出灰度值范围与输入灰度值范围相同。

  在概率论中有 p y ( y ) = p x ( x ) ∣ f − 1 ( y ) ′ ∣ p_{y}(y)=p_{x}(x)\left | f^{-1}(y)^{'} \right | py(y)=px(x)f1(y)这一公式,

f ( x ) = ( L − 1 ) ∫ 0 x p x ( x ) d x f(x)=(L-1)\int_{0}^{x}p_{x}(x)dx f(x)=(L1)0xpx(x)dx,且有 f − 1 ( y ) ′ = 1 / f ′ ( x ) f^{-1}(y)^{'} =1/f^{'}(x) f1(y)=1/f(x)带入上述公式可得:

p y ( y ) = 1 L − 1 p_{y}(y)=\frac{1}{L-1} py(y)=L11

所以直方图经过均衡化后得到的每个像素值得概率分布是相同的,均是 1 L − 1 \frac{1}{L-1} L11。因为直方图是累计概率的近似,并且处理后不产生新的(超过范围的)灰度级,所以实际的直方图均衡化很少能够得到完全平坦(均匀分布)的直方图,但是其有助于直方图的动态范围的扩展。
用和来代替积分计算:
  在这里插入图片描述
其中 r r r代表灰度值, h ( r ) h(r) h(r)代表该灰度值出现的次数, w 、 h w、h wh代表图像的宽和高。
2.4 自定义的均衡化与API相比较出现的问题
  在比较自定义的均衡化函数和OPENCV的API时,发现API会将输入图像的灰度值中最小灰度值的统计数自动忽略,最小灰度值位置处输出结果为0,其他位置的灰度值计算方式是:
  在这里插入图片描述
其中 h ( r 0 ) h(r_{0}) h(r0)是最小灰度值的统计次数,最小灰度值表示图像中出现的最小的值,不一定是0。

三、直方图的计算与绘制----calcHist()

3.1 原理介绍
  原理:通过遍历图像中的所有灰度值,统计每个灰度值出现的次数,按照灰度值从小到大(0~255)的变化顺序,将统计到的次数依次填放在数组或Mat型变量中,在绘制时,要先将统计次数的大小归一化到直方图的高度范围之内,再进行绘制。

3.2 API介绍

 void calcHist(   const Mat* images, int nimages,const int* channels,
 						 InputArray mask, OutputArray hist, int dims, 
 						 const int* histSize, const float** ranges, 
 						 bool uniform = true, bool accumulate = false );

参数image:输入图像指针,可以用&image表示,该参数可以有多个图像,每个图像要有相同的大小和深度,可以有不同的通道数;
参数nimages:表示输入图像的个数;
参数channels:输入图像的通道索引,可以用数组的方式的表示,其中若有多个输入图像,则第一个输入图像的通道索引为0~image.channels() - 1表示,第二个输入图像的通道索引为image.channels() ~ image.channels() + image1.channels() - 1表示,依此类推;
参数mask:如果该矩阵不为空,那么必须是8-bit的矩阵,与原图相同的尺寸,在图像中,只有被mask覆盖的区域的像素才参与直方图统计,默认为Mat();
参数hist:输出直方图,具有dims个维度;
参数dims:直方图的维度,一定是正值,不超过32;
参数histsize:表示直方图每一维的大小的数组,即指出每一维的直方图的条数;(就是将每个维度分成多少份),这里维度可以理解为通道;
参数ranges:表示直方图每一维的每个bin的上下界范围数组的数组,当直方图的bin组距是均匀时,对每一维指定上下界,下界(包含)是第0个bin的边界,上界(不包含)是第(histsize[i] - 1)个bin的边界,也就是说均匀直方图的每个range是由两个元素的数组组成,而每个ranges是由n维个range数组组成;
参数uniform:用于说明直方条bin(组距)是否是均匀等宽的;
参数accumulate:如果已设置,那么重新分配直方图时不会清零之前的,此功能可以从多个数组中计算单个直方图。

3.2.1 个别参数的理解

参数ranges的理解:
在这里插入图片描述
参数dims的理解:
  当 d i m s = i dims=i dims=i 时,得到的输出结果是一个 i i i 维的(Mat型)数组。
  例如:当计算一维直方图时,计算得到的结果是一个相当于一维的矩阵(Mat),列(x)坐标表示灰度值,行(y)坐标表示统计值;
  当计算H-S二维直方图时,计算得到的结果是一个二维的矩阵(Mat),若设置channels[] = {0,1},则对应直方图的行(y)、列(x)坐标分别表示色调(hue)、饱和度(saturation);若设置channels[] = {1,0},则对应直方图的行(y)、列(x)坐标分别表示饱和度(saturation)、色调(hue);
  计算三维直方图时,dims维度对应顺序是y轴,x轴,z轴。

四、直方图对比----compareHist()

直方图比较,是用一定的标准来判断两个直方图的相似度方法;
4.1、API介绍

double compareHist( InputArray H1, InputArray H2, int method );

参数H1:第一个直方图;
参数H2:第二个直方图,和第一个有相同的大小;
参数method:比较方式。
该函数返回的是距离值d(H1,H2)

4.2、常用的四种比较方法
在这里插入图片描述
在这里插入图片描述
方法1的结果越接近1则越相似,方法2的结果越接近0则越相似,方法3的结果越大则越相似,方法4的结果越接近0则越相似。
4.3、函数实现步骤
①先用cvtColor()把图像从RGB色彩空间转换到HSV色彩空间;
②计算图像的直方图,然后归一化到[0~1]之间,用到函数calcHist()和normalize();
③使用上面4种对比标准来计算图像直方图的相似度:

五、直方图的方向投影----calcBackProject()

  反向投影是反映直方图模型在目标图像中的分布情况;简单点说就是用直方图模型去目标图像中寻找是否有相似的对象。通常用HSV色彩空间的HS两个通道直方图模型。
5.1、实现步骤:
①转化色彩空间到HSV;
②计算图像直方图;
③对测试图像中每个像素,获取直方图所对应的统计次数,将该值返回到测试图像的像素位置,依次遍历得到反向投影图。
可以通过对比(compareHist)两幅图像的反向投影矩阵的相似度,判断两幅图的特征相似。
原理:
  原图----原图直方图(建立灰度值与统计值的映射关系)----反向投影图
反向投影中某点的值就是它所对应的原图像中的点所在区间的灰度直方图的值,所以一个区间内的点越多,其反向投影值就越大,显示越亮,这就是提取到的特征。

例如: 给定一个数组:src = {1,2,3,4,5, 2,2,3,3,4,5,5,6,7,8,1,2,3,9,5}
将其区间划分为[0,2],[3,5],[6,8],[9,11],可以得到其直方图值为6,10,3,1
反向投影和原图的灰度图有相同的大小和深度,原图中坐标为(0,0)的值是1,其所在区间的直方图值为6,所以反向投影坐标(0,0)处的值为6,依次类推。(这里这个区间是由bin来划分的

5.2、API介绍

void calcBackProject( const Mat* images, int nimages,
                                 const int* channels, InputArray hist,
                                 OutputArray backProject, const float** ranges,
                                 double scale = 1, bool uniform = true );

参数image:可以有一个或多个图像,大小和深度相同,通道数可以不同;
参数nimage:输入图像个数;
参数channels:输入的通道索引;
参数hist:输入图像的直方图;
参数backProject:输出反向投影图,和原图的灰度图一样的大小和深度;
参数ranges:包含一个或多个设置边界参数数组的数组;
参数scale:灰度值的缩放倍数;
参数uniform:直方图组距是否均匀。

### SIFT 特征匹配算法 Python 实现流程 #### 1. 构建尺度空间 构建多尺度的高斯金字塔,通过不同σ值的高斯核对原始图像进行平滑处理。随后计算相邻层之间的差异得到DOG(Difference of Gaussian)图像[^2]。 ```python def build_gaussian_pyramid(image, num_octaves=4, scales_per_octave=5): gaussian_images = [] for octave_index in range(num_octaves): images_at_octave = [image] sigma = 1.6 * (2 ** octave_index) k = 2 ** (1 / scales_per_octave) for scale_index in range(1, scales_per_octave + 3): kernel_size = int(2 * round(sigma) + 1) image_blurred = cv2.GaussianBlur(images_at_octave[-1], (kernel_size, kernel_size), sigmaX=sigma, sigmaY=sigma) images_at_octave.append(image_blurred) sigma *= k gaussian_images.extend(images_at_octave[:-2]) return np.array(gaussian_images) def compute_dog_images(gaussian_images): dog_images = [] for i in range(len(gaussian_images)-1): dog_image = cv2.subtract(gaussian_images[i+1],gaussian_images[i]) dog_images.append(dog_image) return np.array(dog_images) ``` #### 2. 寻找极值点 在DOG图像中定位可能的关键点位置,这些点是在其邻域内具有极大值或极小值的位置。这一步骤有助于识别稳定且显著的特征点[^3]。 ```python def find_extrema_points(dog_images, contrast_threshold=0.04, edge_ratio=10, double_edge_threshold=True): keypoints = [] for layer_idx in range(1,len(dog_images)-1): prev_layer = dog_images[layer_idx-1] curr_layer = dog_images[layer_idx] next_layer = dog_images[layer_idx+1] height,width = curr_layer.shape[:2] for y in range(1,height-1): for x in range(1,width-1): pixel_value = curr_layer[y,x] is_maxima = all(pixel_value >= neighbor for neighbor in get_neighbors(curr_layer,y,x)) is_minima = all(pixel_value <= neighbor for neighbor in get_neighbors(curr_layer,y,x)) if not(is_maxima or is_minima): continue # Additional checks... keypoint = cv2.KeyPoint(x=x*2**(layer_idx//scales_per_octave), y=y*2**(layer_idx//scales_per_octave), size=2*np.pi*(sigma*k)**2, angle=-1, response=np.abs(pixel_value), octave=layer_idx+scales_per_octave*(octave_index+1), class_id=-1) keypoints.append(keypoint) return keypoints ``` #### 3. 计算主方向 对于每一个找到的关键点,在一定范围内统计梯度直方图来决定该关键点的主要朝向。 ```python def assign_orientations(keypoints, gaussians, radius_factor=3, bins=36, peak_ratio=0.8, magnitude_threshold=0.01): modified_keypoints = [] for keypoint in keypoints: orientation_hist = create_histogram_of_gradients(keypoint, gaussians[keypoint.octave]) max_peak = np.max(orientation_hist) for bin_index in range(bins): left_bin = (bin_index - 1)%bins right_bin = (bin_index + 1)%bins interpolated_peak = interpolate_peak(bin_index,left_bin,right_bin,orientation_hist,bins) if(interpolated_peak > magnitude_threshold and \ interpolated_peak >= peak_ratio * max_peak): new_angle = 360.0 * (interpolated_peak % bins)/float(bins) kp_copy = copy.deepcopy(keypoint) kp_copy.angle = new_angle modified_keypoints.append(kp_copy) return modified_keypoints ``` #### 4. 描述符生成 基于每个关键点及其周围像素的信息创建描述符。通常采用128维浮点数组表示一个SIFT特征描述子。 ```python def generate_descriptors(keypoints_with_orientation, gaussians, window_width=4, n_bins=8, scale_multiplier=3, descriptor_max_val=0.2): descriptors = [] for keypoint in keypoints_with_orientation: oriented_grad_magnitude,oriented_grad_direction = calculate_gradient_magnitudes_and_angles(keypoint,gaussians[keypoint.octave]) histogram_tensor = construct_histogram_tensor(window_width,n_bins,scale_multiplier,keypoint,oriented_grad_magnitude,oriented_grad_direction) normalized_descriptor = normalize_descriptor(histogram_tensor,descriptor_max_val) descriptors.append(normalized_descriptor.flatten()) return np.array(descriptors).astype(np.float32) ``` #### 5. 进行特征匹配 利用KD树结构快速检索最接近邻居,并应用比率测试去除不稳定的匹配关系[^4]。 ```python bf = cv2.BFMatcher() matches = bf.knnMatch(desc1, desc2, k=2) good_matches = [] for m, n in matches: if m.distance < 0.7*n.distance: # Ratio test as per Lowe's paper good_matches.append([m]) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值