总结
传统方法找线的工作量在单一环境下是少于深度学习的。但是遇到复杂情况,深度学习的工作量远小于传统方法。
传统方法缺陷分析
传统方法在复杂环境下存在的本质问题是,传统方法的假设,比如我看了最新的论文Illumination Invariant Imaging— Applications in Robust Vision-based。他里面假设不同光源相同表面产生的像素值结果是I1B=aI2*B,其中I1和I2是光源的强度,a是一个比例系数。但是实际情况下远远比这个模型复杂多了,实际可能是B(I1),且这个函数是个非线性函数,因此传统方法的假设过强。
传统方法只能找那些具有强特征的目标。本质上说就是传统方法假设条件过于理想,就像纯数学证明有很多在现实不成立的假设,数学建模会把很多影响小的因素给忽略不计。
传统方法可以取巧的解决某些问题,比如图片里找太阳那种的,我就找最亮的对不,而且大部分情况都是ok的。但是找个那种不那么明显的,且环境复杂,则存在问题。
为什么说图片是非线性的?因为有的像素RGB为90,50,30;而有的像素是50,20,0,而且都是线上的像素。因此这个函数极其复杂而不是线性的。
最后也说出了传统方法找线的方法,只是我认为这种方法的工作量不小于深度学习方法,且其拓展能力小于深度学习方法。
找线
方法1
找到标准线的像素,通过计算3通道图像中像素与便准线像素的差值,取平均后再画出直方图,可以得到一个阈值,这个阈值代表了差值多少值以内是一根线。
destiny_img_original = cv2.imread('D:\zy\data\windpipe_second_ligation\windpipe_img/2020_9_26_Trim.mp4588.jpg', 1)
# 得到目标黑线的像素均值
black_line_one_pixel = get_black_line_pixel()
# 将目标图片进行拷贝
destiny_img = destiny_img_original.copy()
destiny_img_h, destiny_img_w = destiny_img.shape[:2]
Histogram_loss_img=np.mean(np.abs(destiny_img-black_line_one_pixel),2)
Histogram_loss=np.histogram(Histogram_loss_img, 50, [0, 256])
Histogram_loss
(array([ 36, 180, 536, 198, 1562, 2829, 2322, 1766, 1618, 972, 829,
835, 1054, 1488, 1147, 758, 982, 1013, 1054, 337, 20, 14,
6, 4, 2, 5, 2, 2, 3, 3, 1, 1, 2,
3, 3, 6, 12, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0], dtype=int64), array([ 0. , 5.12, 10.24, 15.36, 20.48, 25.6 , 30.72, 35.84,
40.96, 46.08, 51.2 , 56.32, 61.44, 66.56, 71.68, 76.8 ,
81.92, 87.04, 92.16, 97.28, 102.4 , 107.52, 112.64, 117.76,
122.88, 128. , 133.12, 138.24, 143.36, 148.48, 153.6 , 158.72,
163.84, 168.96, 174.08, 179.2 , 184.32, 189.44, 194.56, 199.68,
204.8 , 209.92, 215.04, 220.16, 225.28, 230.4 , 235.52, 240.64,
245.76, 250.88, 256. ])
在这个结果情况下,我觉得差10或20以内是线。
假设线都是从上向下的、很细的,如图
因此在一个起始点,先横向遍历,若连续5个像素都是同一物体,则认为物体过粗,不是线,只保留是线的像素。这一步的目的是假如有些地方的背景也是黑的情况下可以删除这些情况。
遍历完全部图片后,再纵向遍历,只有距离很近的,如小于5点距离的才是一条线。最后保留长度很长的即是线。
结果
失败,原因是像素值变化范围较大,取固定差值有问题,
方法2
每一行与目标像素值做差,取最接近的点,最后得到目标线。实验效果大部分较好,但是对于颜色相似区域会造成干扰。
def get_black_line_pixel():
black_line_img = cv2.imread('black_line.png', 1)
black_line_img = black_line_img[1, :3, :]
blue_mean = black_line_img[:, 0].mean()
green_mean = black_line_img[:, 1].mean()
red_mean = black_line_img[:, 2].mean()
line_pixle = np.array([blue_mean, green_mean, red_mean])
return line_pixle
def file_process_f(process_function, save_dir):
img_dir = r'D:\zy\data\windpipe_second_ligation\windpipe_img'
img_names = os.listdir(img_dir)
if not os.path.exists(save_dir):
os.makedirs(save_dir)
for i in range(len(img_names)):
tempt_img = cv2.imread(img_dir + '\\' + img_names[i], 1)
concatenated_img = process_function(tempt_img)
cv2.imwrite(save_dir + '\\' + img_names[i], concatenated_img)
def black_line_f(destiny_img_original):
line_one_pixel = get_black_line_pixel()
destiny_img = destiny_img_original.copy()
destiny_img_h, destiny_img_w = destiny_img.shape[:2]
index_mask = destiny_img - line_one_pixel
index_mask = np.mean(index_mask, 2)
black_line_axe1_index = np.argmin(index_mask, 1)
black_line_axe0_index = np.arange(destiny_img_h)
show_img = np.zeros([destiny_img_h, destiny_img_w], np.uint8)
show_img[black_line_axe0_index, black_line_axe1_index] = 254
kernel = np.ones((3, 3), np.uint8)
show_img = cv2.morphologyEx(show_img, cv2.MORPH_CLOSE, kernel)
show_img = np.stack([show_img, show_img, show_img], 2)
show_img = np.concatenate([show_img, destiny_img_original], 0)
return show_img
方法3
(1)首先,设计一个算法,可以自适应的在每行找到一个阈值,若两个像素的差值大于这个阈值,则说明是两种物体,否则是一个物体。
同一个物体,且和周围的不一样的线,就会变成两边黑中间白。根据黑到黑之间横向像素的位置距离,因为线很细,因此可以判断出线。且对线的颜色不敏感。
最后呢,白线还是不能判断,这种情况太难受。
方法4
第一步,canny找边缘。找到边缘后,认为同一行的两个边缘之间是同一物体,求此物体的宽度,若很宽则删除,最后得到结果为:
第二部:根据线的颜色再进行过滤,取与目标线颜色差值最小的点
第三步:根据线的连续性过滤,选取最长的,最连续的。比如用像素的坐标作为判断依据,来个最短路径算法。
方法5
作题又看了下颜色相关知识,然后弄了个代码,可以观察图像的像素:
import cv2
img = cv2.imread(r'D:\zy\data\windpipe_second_ligation\test_imgs\test_ouputs\windpipe_img_histogram_compare\wangguangliang.mp478.jpg') # 定义图片位置
# img= cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #转化为灰度图 因为我读取的直接是灰度标签图片就不用转化了
def onmouse(event, x, y, flags, param): # 标准鼠标交互函数
if event == cv2.EVENT_MOUSEMOVE: # 当鼠标移动时
print('R:'+str(img[y,x,2]),'G:'+str(img[y,x,1]),'B:'+str(img[y,x,0])) # 显示鼠标所在像素的数值,注意像素表示方法和坐标位置的不同
def main():
cv2.namedWindow("img") # 构建窗口
cv2.setMouseCallback("img", onmouse) # 回调绑定窗口
while True: # 无限循环
cv2.imshow("img", img) # 显示图像
if cv2.waitKey() == ord('q'): break # 按下‘q'键,退出
cv2.destroyAllWindows() # 关闭窗口
if __name__ == '__main__': # 运行
main()
发现就是黑色不是像素值小,而是像素值均匀且小,这样的才是黑色的。因此接下来用这两个特征来找线。但是有个问题就是多个特征如何合理的组合到一起问题,比如a*A1+b*A2,其中大写的是特征值。那么a和b这个权重怎么选?
于是我想到使用类似强化学习方法,通过自动的使用不同的a和b来得到很多图片,再计算图片中得到的线的程度,如计算相邻像素的空间距离,取值最小的就是正确的a和b的值。
失败,凡是涉及到像素值的都不靠谱,因为光线变了就完犊子了。而且看了一些光线不变算法,里面假设的光源强度和材料反射得到的像素值都是线性关系,比如光强100,反光度0.5,那么结果像素值就是50。其实实际中不是这样的,比如有的黑线像素是50,20,0。假如是这种比例关系根本不可能,因为红色的不可能是蓝色的50倍的强度。
说到底还是假如可调参数超过1个以后,那么传统方法的工作量会几何倍的增长来调参。而且要分的特别明显才行。
方法6
参考深度学习的方法,直接根据图像求出一般情况下线和背景的差值,统计大量的情况,求出差值的期望值和方差,再来做,但是这样的话不如用深度学习啊。
所以我还是用深度学习来解决了,深度学习具有很强的可拓展性,只要再训练数据中加入图片就行了,但是传统的话其实差不多,只是要自己在规则里加入对应情况的参数。
方法7
深度学习方法,考虑到速度的因素因此用一个简单的depth为1-3,width为5-50的卷积网络来做。标了12张图片(我实在讨厌标数据)
实验结果表示效果不好,多方面因素:
(1)线的数据不好标,因为线太细,标不好标,标不准,因此会影响结果
(2)网络无预训练参数、网络太小,无法做更抽象的特征。(由于有速度的要求)
方法8
方案就是canny找边缘
用同物体宽度信息过滤
用同物体长度信息过滤
用大概的颜色特征,如黑线应rgb都小于其旁边像素。利用这3个过滤基本上百分之80的准确率有了,由于是在视频处理的,因此用时间窗口滤波可以将准确率提高到95以上。
# 将目标图片进行拷贝
destiny_img = destiny_img_original.copy()
# 将图片色彩偏置归一化
# normalization_img = self.max_picel_normalization(destiny_img)
# 直方图均值化
# his_img = his_my(destiny_img)
# 用rgb像素的方差来进一步滤掉非黑的像素
# his_img_variance = np.var(his_img, 2)
# his_img_variance_mask = his_img_variance < self.var_max_threshold
# canny处理加用宽度过滤
# his_img_variance_masked_img=img_mask_function(his_img,his_img_variance_mask)
width_processed_mask, canny_output_mask = self.canny_and_width_filter(destiny_img)
# 膨胀腐蚀处理
# kernel = np.ones((3, 3), np.uint8)
# erosion = cv2.erode(width_processed_mask, kernel, iterations=1)
# opening = cv2.morphologyEx(width_processed_mask, cv2.MORPH_CLOSE, kernel)
# 找轮廓
ret, binary = cv2.threshold(width_processed_mask * 200, 127, 255, cv2.THRESH_BINARY)
binary = np.uint8(binary)
img, contours, hierarchy = cv2.findContours(binary, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
#在所有找到的轮廓中,选中那个高度最大的,高度最大说明最长
max_h_value = 0
max_h_index = None
for contour_index in range(len(contours)):
# countours[0].shape=[23,1,2]
max_h = np.max(contours[contour_index][:, 0, 1])
min_h = np.min(contours[contour_index][:, 0, 1])
tempt_height = max_h - min_h
if tempt_height > max_h_value:
max_h_value = tempt_height
max_h_index = contour_index
#在新图片上画出最后结果
new_img = np.zeros_like(img)
new_img = cv2.drawContours(new_img, contours, max_h_index, (255, 255, 255), -1)
判断是否存在线
方法1
用距离加相邻像素差值判断。一条线的像素肯定相邻的像素靠近,相邻像素值接近,因此求出一个代价来描述是一条线的程度。取一个阈值来判断是否是一条线。