限制对比度自适应直方图均衡化(自我理解)

2024-08-27 19:32

本文主要是介绍限制对比度自适应直方图均衡化(自我理解),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

CLAHE算法对于医学图像,特别是医学红外图像的增强效果非常明显。

CLAHE  https://en.wikipedia.org/wiki/Adaptive_histogram_equalization

中文方面非常好的资料 限制对比度自适应直方图均衡化算法原理、实现及效果(我自己直接看解释没怎么看懂,不如直接看本篇博文下面的代码)

在OpenCV中已经实现了CLAHE,但是它在使用过程中,存在参数选择的问题。为了从根本上搞明白,我参考了网络上的一些代码

主要是来源 http://blog.csdn.net/abcd1992719g/article/details/25483395 (我觉的这篇博客的java代码更容易使人理解)

看完上面的java代码,我的理解

首先你得了解直方图均衡化(https://blog.csdn.net/u013066730/article/details/82969768)。

限制对比度自适应直方图均衡化方法步骤为:

将整幅图像切分为5*5的块block(但通常代码中为8*8,但本文以5*5举例),如图一右侧的黑色实线框所示。

                                                 图一

接着计算每个block的累积分布,在计算累积分布时,限制一下他的对比度,别让直方图出现特别陡峭的情况,直观图像就如图二所示。具体操作就是将直方图中高于某个阈值a的数值全都找出来构成一个集合B,然后B中的每一个元素b都变为a,其中b高于a的部分进行求和得到c,c除以256(该256是指灰度等级,也就是0-255)得到d,将d加在每一个灰度等级下,其实就是图二中蓝色尖状部分变为平坦状。

                                  图二

累积分布求好后,就需要将原始图像中的每个像素值通过累积分布映射为新的像素值。其中使用了双线性插值的方法,而在使用双线性插值时,需要将原图重新分配,也就是图一的粉色,绿色和紫色区域。其中粉色区域的像素值就直接使用对应block的累积分布直接计算即可得到新的像素值(这里如何计算可以参考直方图均衡化)。绿色区域的像素值a,使用绿色区域中邻近的2个block的累计分布计算得到全新的2个像素值b1,b2,然后根据a与这两个block的距离比例得到求和比例c1,1-c1,所以原始像素a对应的最终像素d = b1*c1+b2*(1-c1)。紫色区域的像素值p,使用紫色区域中邻近的4个block的累积分布计算得到全新的4个像素值q1,q2,q3,q4,然后像素p根据与这4个block的距离远近得到对应比例,第一个block的比例为u,v(就是图一中紫色方框叉距离第一个黑色实心框的x,y的比例),第二个block的比例为1-u,v,第三个block的比例为u,1-v,第四个block的比例为1-u,1-v,所以原始像素p对应的最终结果i=u*v*q1+(1-u)*v*q2+u*(1-v)*q3+(1-u)*(1-v)*q4。

哎,我这里说的也有点乱,我感觉不如直接花点时间看下面的代码,真是一看就懂。

/* * CLAHE * 自适应直方图均衡化 */  public int[][] AHE(int[][] oldmat,int pblock)  {  int block = pblock;  //将图像均匀分成等矩形大小,8行8列64个块是常用的选择  int width_block = width/block;  int height_block = height/block;  //存储各个直方图  int[][] tmp = new int[block*block][256];  //存储累积函数  float[][] C = new float[block*block][256];  //计算累积函数  for(int i = 0 ; i < block ; i ++)  {  for(int j = 0 ; j < block ; j++)  {  int start_x = i * width_block;  int end_x = start_x + width_block;  int start_y = j * height_block;  int end_y = start_y + height_block;  int num = i+block*j;  int total = width_block * height_block;  for(int ii = start_x ; ii < end_x ; ii++)  {  for(int jj = start_y ; jj < end_y ; jj++)  {  int index = oldmat[jj][ii];  tmp[num][index]++;  }  }  //裁剪操作  int average = width_block * height_block / 255;  int LIMIT = 4 * average;  int steal = 0;  for(int k = 0 ; k < 256 ; k++)  {  if(tmp[num][k] > LIMIT){  steal += tmp[num][k] - LIMIT;  tmp[num][k] = LIMIT;  }  }  int bonus = steal/256;  //hand out the steals averagely  for(int k = 0 ; k < 256 ; k++)  {  tmp[num][k] += bonus;  }  //计算累积分布直方图  for(int k = 0 ; k < 256 ; k++)  {  if( k == 0)  C[num][k] = 1.0f * tmp[num][k] / total;  else  C[num][k] = C[num][k-1] + 1.0f * tmp[num][k] / total;  }  }  }  int[][] new_mat = new int[height][width];  //计算变换后的像素值  //根据像素点的位置,选择不同的计算方法  for(int  i = 0 ; i < width; i++)  {  for(int j = 0 ; j < height; j++)  {  //four coners  if(i <= width_block/2 && j <= height_block/2)  {  int num = 0;  new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);  }else if(i <= width_block/2 && j >= ((block-1)*height_block + height_block/2)){  int num = block*(block-1);  new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);  }else if(i >= ((block-1)*width_block+width_block/2) && j <= height_block/2){  int num = block-1;  new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);  }else if(i >= ((block-1)*width_block+width_block/2) && j >= ((block-1)*height_block + height_block/2)){  int num = block*block-1;  new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);  }  //four edges except coners  else if( i <= width_block/2 )  {  //线性插值  int num_i = 0;  int num_j = (j - height_block/2)/height_block;  int num1 = num_j*block + num_i;  int num2 = num1 + block;  float p =  (j - (num_j*height_block+height_block/2))/(1.0f*height_block);  float q = 1-p;  new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);  }else if( i >= ((block-1)*width_block+width_block/2)){  //线性插值  int num_i = block-1;  int num_j = (j - height_block/2)/height_block;  int num1 = num_j*block + num_i;  int num2 = num1 + block;  float p =  (j - (num_j*height_block+height_block/2))/(1.0f*height_block);  float q = 1-p;  new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);  }else if( j <= height_block/2 ){  //线性插值  int num_i = (i - width_block/2)/width_block;  int num_j = 0;  int num1 = num_j*block + num_i;  int num2 = num1 + 1;  float p =  (i - (num_i*width_block+width_block/2))/(1.0f*width_block);  float q = 1-p;  new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);  }else if( j >= ((block-1)*height_block + height_block/2) ){  //线性插值  int num_i = (i - width_block/2)/width_block;  int num_j = block-1;  int num1 = num_j*block + num_i;  int num2 = num1 + 1;  float p =  (i - (num_i*width_block+width_block/2))/(1.0f*width_block);  float q = 1-p;  new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);  }  //inner area  else{  int num_i = (i - width_block/2)/width_block;  int num_j = (j - height_block/2)/height_block;  int num1 = num_j*block + num_i;  int num2 = num1 + 1;  int num3 = num1 + block;  int num4 = num2 + block;  float u = (i - (num_i*width_block+width_block/2))/(1.0f*width_block);  float v = (j - (num_j*height_block+height_block/2))/(1.0f*height_block);  new_mat[j][i] = (int)((u*v*C[num4][oldmat[j][i]] +   (1-v)*(1-u)*C[num1][oldmat[j][i]] +  u*(1-v)*C[num2][oldmat[j][i]] +  v*(1-u)*C[num3][oldmat[j][i]]) * 255);  }  new_mat[j][i] = new_mat[j][i] + (new_mat[j][i] << 8) + (new_mat[j][i] << 16);         }  }  return new_mat;  }  

 

这篇关于限制对比度自适应直方图均衡化(自我理解)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1112574

相关文章

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

poj 2135 有流量限制的最小费用最大流

题意: 农场里有n块地,其中约翰的家在1号地,二n号地有个很大的仓库。 农场有M条道路(双向),道路i连接着ai号地和bi号地,长度为ci。 约翰希望按照从家里出发,经过若干块地后到达仓库,然后再返回家中的顺序带朋友参观。 如果要求往返不能经过同一条路两次,求参观路线总长度的最小值。 解析: 如果只考虑去或者回的情况,问题只不过是无向图中两点之间的最短路问题。 但是现在要去要回

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

poj 3422 有流量限制的最小费用流 反用求最大 + 拆点

题意: 给一个n*n(50 * 50) 的数字迷宫,从左上点开始走,走到右下点。 每次只能往右移一格,或者往下移一格。 每个格子,第一次到达时可以获得格子对应的数字作为奖励,再次到达则没有奖励。 问走k次这个迷宫,最大能获得多少奖励。 解析: 拆点,拿样例来说明: 3 2 1 2 3 0 2 1 1 4 2 3*3的数字迷宫,走两次最大能获得多少奖励。 将每个点拆成两个

poj 2195 bfs+有流量限制的最小费用流

题意: 给一张n * m(100 * 100)的图,图中” . " 代表空地, “ M ” 代表人, “ H ” 代表家。 现在,要你安排每个人从他所在的地方移动到家里,每移动一格的消耗是1,求最小的消耗。 人可以移动到家的那一格但是不进去。 解析: 先用bfs搞出每个M与每个H的距离。 然后就是网络流的建图过程了,先抽象出源点s和汇点t。 令源点与每个人相连,容量为1,费用为

poj 3068 有流量限制的最小费用网络流

题意: m条有向边连接了n个仓库,每条边都有一定费用。 将两种危险品从0运到n-1,除了起点和终点外,危险品不能放在一起,也不能走相同的路径。 求最小的费用是多少。 解析: 抽象出一个源点s一个汇点t,源点与0相连,费用为0,容量为2。 汇点与n - 1相连,费用为0,容量为2。 每条边之间也相连,费用为每条边的费用,容量为1。 建图完毕之后,求一条流量为2的最小费用流就行了

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是