自动求取最佳阈值的方法:最右阈值法
最右阈值法:找到图像直方图的各个波峰和波谷的位置,然后取最右边的波谷的位置作为最佳阈值,如图:
- 读取样例图片并调用处理方法:
采用L5.jpg如图
#ImgPreProc.py
# -*-coding:utf-8 -*-
# @Author : Ming
import cv2 as cv
import numpy as np
import PeakAndWave
import histogram as hg
def preProcPlot(img):
#对比度和亮度,参数自己调节
src=hg.contrast_brightness_image(img,1.02,0.5)
#计算原图直方图
hist=hg.cal_histogram(src)
#对直方图进行卷积平滑
smooth_hist=np.array(hg.convolution_smooth(hist))
#计算一阶差分
first_dif=PeakAndWave.first_dif(smooth_hist)
#消除一阶差分的微小抖动
smooth_first_dif=PeakAndWave.dis_slight_jitter(first_dif,2)
peak,wave=PeakAndWave.peakAndwaveNew(smooth_first_dif,5,9)
#消除相似点 peak,wave=PeakAndWave.remove_similarPoints(peak),PeakAndWave.remove_similarPoints(wave)
im_new=PeakAndWave.img_bz(src,wave[len(wave)-1])
cv.imwrite(r'E:/dev_lab/pumch/img_pre/result.jpg',im_new)
return im_new
- 画直方图和对直方图进行卷积平滑
对直方图进行卷积平滑而不是对原图像进行降噪处理,这样取得的直方图的波谷更佳。
#histogram.py
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# 粗略的调节对比度和亮度
def contrast_brightness_image(img, a, g):
try:
h,w= img.shape# 获取shape的数值,height和width、通道
# 新建全零图片数组src2,将height和width,类型设置为原图片的通道类型(色素全为零,输出为全黑图片)
src2 = np.zeros([h, w], img.dtype)
dst = cv.addWeighted(img, a, src2, 1 - a, g) # addWeighted函数说明如下
return dst
except BaseException as e:
print('contrast_brightness_image :',e)
#画直方图
def cal_histogram(img):
hist=cv.calcHist([img],[0],None,[256],[0,256])
plt.figure() # 新建一个图像
plt.title(u"原直方图") # 图像的标题
plt.xlabel("Bins") # X轴标签
plt.ylabel("# of Pixels") # Y轴标签
plt.plot(hist) # 画图
plt.xlim([0, 256]) # 设置x坐标轴范围
plt.show() # 显示图像
return hist
#卷积(权重)法对直方图平滑
"""卷积函数 cv2.filter2D(img,-1,kernel) 第二个参数目标图像的所需深度。如果是负数,则与原图像深度相同 第三个参数是卷积内核"""
def convolution_smooth(img):
#将图像转换成数值类型
img1 = np.float32(img)
kernel = np.ones((4,4), np.float32) / 16
dst = cv.filter2D(img1,-1, kernel)
plt.figure() # 新建一个图像
plt.xlabel("Bins") # X轴标签
plt.ylabel("# of Pixels") # Y轴标签
plt.title(u"卷积平滑处理后的波峰波谷位置")
plt.xlim([0, 256])
plt.plot(dst)
plt.show()
return dst
样图的原直方图:
经过卷积平滑处理后的直方图:
3. 求波峰波谷
#PeakAndWave.py
# _*_ coding:utf-8 _*_
# @Author : Ming
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
from enum import Enum
#阈值二值化
def img_bz(img,val):
""" cv2.THRESH_BINARY;大于阈值的像素点的灰度值设定为最大值(如8位灰度值最大为255),灰度值小于阈值的像素点的灰度值设定为0。
"""
ret,thresh=cv.threshold(img,val,255,cv.THRESH_BINARY )
return thresh
#plt.rcParams['font.sans-serif'] = ['SimHei']
def first_dif(arr):
'''
计算一阶差分
:param arr:
:return:
'''
L = []
for i in range(1, len(arr)):
L.append(arr[i] - arr[i - 1])
L.append(0)
return L
def dis_slight_jitter(fd_arr, val):
'''
消除一阶差分的微小抖动,绝对值小于val的判定为0
:param arr:
:param val:
:return:
'''
L = []
for i in range(0, len(fd_arr)):
if abs(fd_arr[i]) <= val:
L.append(0)
else:
L.append(fd_arr[i])
return L
def val_miner(val):
'''
如果 val >0 返回1;val =0 返回 0;val 小于0 返回 -1
'''
return 1 if val >= 0 else (-1 if val < 0 else 0)
def arrounding(arr, i, n):
'''
计算i点左右各n个数的归一化值
:param arr:
:param i: 当前位置
:param n:
:return:
'''
left_dif_total, right_dif_total = 0, 0 # 临时存储一段变化的值
for k in range(0, n): # 用判断器计算当前点左边和右边的变化(累加)情况
left_dif_total = left_dif_total + val_miner(arr[i - k])
right_dif_total = right_dif_total + val_miner(arr[i + k + 1])
return left_dif_total, right_dif_total
#定义方向:左边或者右边
class Direction(Enum):
LEFT = -1
RIGHT = 1
#定义变化趋势:增或者减
class Ratio(Enum):
PLUS = 1
MINUS = -1
def trend_res_old(arr, i, direction, step, ratio):
'''
判断数组arr[i] 的位置 前面或者后面是不是有 step个 数连续增或者减
:param arr:
:param i: 位置
:param direction:-1 前面;1 后面
:param step:
:param ratio: 斜率,表示增或者减. -1 为减 1 为增
:return:
'''
total=0
for k in range(0, step):
if i < k or i + k + 1 > len(arr) - 1:
continue;
if direction is Direction.LEFT: # 左边
val = arr[i - k]
else:
val = arr[i + k + 1]
total = total + val
if ratio is Ratio.PLUS: # 增
if val < 0: return False
elif ratio == -1:
if val > 0: return False
#如下两行过滤掉增量非常小的波峰和波谷
if ratio is Ratio.PLUS and int(total / step) < 3:
return False
if ratio is Ratio.MINUS and int(total / step) > -3:
return False
return True
def trend_res(arr, i, direction, step, ratio):
'''
判断数组arr[i] 的位置 前面或者后面是不是有 step个 数连续增或者减
:param arr:
:param i: 位置
:param direction:-1 前面;1 后面
:param step:
:param ratio: 斜率,表示增或者减. -1 为减 1 为增
:return:
'''
total,count=0,0 #total:总数;counter:增或者减的计数器
for k in range(0, step):
if i < k or i + k + 1 > len(arr) - 1:
continue;
if direction is Direction.LEFT: # 左边
val = arr[i - k]
else:
val = arr[i + k + 1]
total = total + val
if ratio is Ratio.PLUS: # 增
if val >= 0:
count = count + 1
else:
count = count - 1
elif ratio is Ratio.MINUS: # 减
if val <= 0:
count = count + 1
else:
count = count - 1
# if abs(int(total / step))<3:
# return False
return count > (count / 2 + 1)
def remove_similarPoints_new(smooth_hist,arr,mod, step=8):
'''
:param smooth_hist: 平滑后的直方图
:param arr: 波峰或则波谷的数组
:param mod: 'PEAK|WAVE' 波峰或者波谷
:param step: 表示连续,两个波峰或者波谷的距离最小值
:return:
'''
arr_new, seg_ments, continuous_list = [], [], []
if len(arr) >= 1:
arr = sorted(arr)
for i in range(1, len(arr)):
if abs(arr[i] - arr[i - 1]) <= step: # 两个波峰或者波谷位置间距< step 扔到todo_list中
continuous_list.append(arr[i - 1])
else: # 如果需要处理的todo_list 集合>0 ,则处理todolist
continuous_list.append(arr[i - 1])
seg_ments.append(continuous_list)
continuous_list = []
if arr[-1] - arr[-2] <= step: # 判断最后一个点
seg_ments[-1].append(arr[-1])
else:
seg_ments.append([arr[-1]])
for i in range(0, len(seg_ments)):
arr_new.append(selectAppropriate(smooth_hist, seg_ments[i], mod))
return arr_new
else:
return arr
#从group中选出最好的一个位置
def selectAppropriate(smooth_hist,group,mod):
'''
:param smooth_hist:平滑后的直方图
:param group:待处理的组
:param mod:'PEAK|WAVE' 波峰或者波谷
:return:
'''
ylist,xlist=[],[] #ylist存放直方图y轴的值,即某一灰度个数;xlist存放 一组group中 波谷或者波峰的点,可能为多个,所以最后返回中间那个
for n in range(0, len(group)):
ylist.append(smooth_hist[group[n]])
val = (np.max(ylist) if mod == 'PEAK' else np.min(ylist))# 如果波峰取最大值;波谷取最小值
for n in range(0, len(group)):
if val == smooth_hist[group[n]]:
xlist.append(group[n]) # 把y值相等的点放到temp中作为候选点
if len(xlist) == 1: # 候选点个数为1,直接加到arr_new中
return xlist[0]
else: # 候选点个数>1 ,取中间点添加到arr_new中
return xlist[len(xlist) / 2]
# 删除拐点中距离比较近的点
def remove_similarPoints(arr, step=8):
del_list = []
if len(arr) > 1:
arr = sorted(arr)
for i in range(1, len(arr)):
if abs(arr[i] - arr[i - 1]) <= step:
del_list.append(arr[i-1])
for i in del_list:
arr.remove(i)
return arr
else:
return arr
def peakAndwave(first_dif, dif_score, stable_score):
'''
求波峰和波谷
波谷:一阶差分连续11个为0(经验观察值),则取连续n个0中间的地方为波谷;或者左侧超过连续5个小于0,然后右侧超过连续5个大于0;
波峰:左侧连续超5个大于0,右侧连续超5个小于0
:param first_dif: 一阶差分数组,反应出变化趋势
:param dif_score: 发生变化的前后连续变化的点数>这个值时,表示可能到了波峰或者波谷的位置
:param stable_score: 连续n个0时,表示可能处于波谷位置
:return:
'''
peak, wave = [], []
for i in range(dif_score, len(first_dif) - dif_score):
if first_dif[i] > 0 and first_dif[i + 1] <= 0: # 左边增,右边减,可能是波峰
if trend_res(first_dif, i, Direction.LEFT, dif_score, Ratio.PLUS) and trend_res(first_dif, i, Direction.RIGHT, dif_score, Ratio.MINUS):
peak.append(i + 1)
elif first_dif[i] < 0 and first_dif[i + 1] >= 0: # 左边减,右边增,可能是波谷
if trend_res(first_dif, i, Direction.LEFT, dif_score, Ratio.MINUS) and trend_res(first_dif, i, Direction.RIGHT, dif_score, Ratio.PLUS):
wave.append(i)
elif first_dif[i] == 0 and first_dif[i + 1] != 0 and trend_res(first_dif, i, Direction.RIGHT, dif_score, Ratio.PLUS):
zero_total = 0
for n in range(1, i):
if first_dif[i - n] == 0:
zero_total = zero_total + 1
else:
break
if zero_total >= stable_score: # 连续很多个0,处于波谷时,取连续0中间的位置为波谷
wave.append(i - zero_total / 2)
return peak, wave
def peakAndwaveNew(first_dif, dif_score, stable_score):
peak, wave = [], []
for i in range(1, len(first_dif) - 1):
if first_dif[i] * first_dif[i + 1] < 0 or (first_dif[i] == 0 and first_dif[i + 1] != 0) or (
first_dif[i] != 0 and first_dif[i + 1] == 0): # 符号发生变化
RIGHT_PLUS = trend_res(first_dif, i, Direction.RIGHT, dif_score, Ratio.PLUS)
RIGHT_MINUS = trend_res(first_dif, i, Direction.RIGHT, dif_score, Ratio.MINUS)
if RIGHT_PLUS:
if trend_res(first_dif, i, Direction.LEFT, dif_score, Ratio.MINUS):
wave.append(i + 1)
if RIGHT_MINUS:
if trend_res(first_dif, i, Direction.LEFT, dif_score, Ratio.PLUS):
peak.append(i + 1)
zero_total = 0
for n in range(1, i):
if first_dif[i - n] == 0:
zero_total = zero_total + 1
else:
break
if zero_total >= stable_score: # 连续很多个0,处于波谷时,取连续0中间的位置为波谷
if RIGHT_PLUS:
wave.append(i - zero_total / 2 + 1)
elif RIGHT_MINUS:
peak.append(i - zero_total / 2 + 1)
return peak, wave
- test方法测试
import cv2 as cv
import ImgPreProc
img=cv.imread(r'E:/dev_lab/pumch/img_pre/L3.jpg',0)
cv.imshow('img',img)
img_res=ImgPreProc.preProcPlot(img)
cv.imshow('img_res',img_res)
cv.waitKey(0)
cv.destroyAllWindows()
处理后的图片如图所示: