欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

对象发现

程序员文章站 2022-06-18 16:15:00
对象发现 不知道什么原因博客园在markdown编辑器上无法上传图片。需要看源码和图片复原实验的可以去我的 "github" 近段时间,做了一些关于对象发现的工作。主要内容是从图片中识别出液滴,并统计其数量。在这个过程中遇到了一些问题,也发现了几种相关的解决方案,在这里与大家分享一下。 python ......

对象发现

不知道什么原因博客园在markdown编辑器上无法上传图片。需要看源码和图片复原实验的可以去我的github

近段时间,做了一些关于对象发现的工作。主要内容是从图片中识别出液滴,并统计其数量。在这个过程中遇到了一些问题,也发现了几种相关的解决方案,在这里与大家分享一下。

python中用来处理图像的不得不说cv2 了,这是一个工业级的包。包含了几乎所有的图片处理方法,例如常见的找边界、膨胀、腐蚀、画矩形、画圆等。本次实践过程我使用到了三种方法,用来识别图像中的液滴。第一种是常规的处理方法,先对图片进行边界确定,然后膨胀、腐蚀,最后寻找液滴;第二种方法是由于第一中方法会将边缘勿识别为液滴,故在第一种方法的基础上加上边缘界定,想借此消除边界误差;第三种是用多个液滴模板,一次对图片进行匹配,如果相似度在阈值内则认为是目标液滴,否则不是。

实例背景:我们需要从一张显微镜拍下的分液图中寻找出液滴,并统计数量。

1. 常规方法--膨胀+腐蚀  
2. 先界定边缘然后膨胀+腐蚀  
3. 遮罩匹配  

常规方法

常规方法中,关键在与图片处理的流程。在这里,我们的图片存在色差不明显和颜色偏淡的情况,为此,我们首先对图片做颜色增强操作。(此前对比了锐化操作,对结果没有明显的提升)接着开始做寻找图片中对象的操作。首先,使用cv2.cvtcolor方法将图片转化为灰度图,使用cv2.canny 方法找出对象的边界;然后,使用cv2.dilate 方法进行膨胀操作;紧接着,使用cv2.erode 方法进行腐蚀操作;最后,使用cv2.findcontours 方法找出图片中的对象。在可视化部分,我们使用cv2.drawcontours 方法绘出我们找到的对象,与实际情况对比,观察误差存在的区域以及特点。下面给出每一步操作的代码。

导入包
import numpy as np
from pil import imageenhance,image
import cv2
import os
import sys

首先我们定义了颜色增强的方法

def enhanceimage(image):
#色度增强
enh_col = imageenhance.color(image);
color = 1.5;
image_colored = enh_col.enhance(color);

return np.array(image_colored);

image_enhance = enhanceimage(image);

找到对象轮廓

image_to_process = image_enhance[:,:,:3];
image_gray = cv2.cvtcolor(image_to_process, cv2.color_bgr2gray);
image_cannyedged = cv2.canny(image_gray, 50, 120);

膨胀

dilatekernel = cv2.getstructuringelement(cv2.morph_rect,(3,3)); #定义膨胀核的形状
image_dilateedged = cv2.dilate(image_cannyedged, dilatekernel, iterations=2); #膨胀

腐蚀

errodekernel = cv2.getstructuringelement(cv2.morph_ellipse,(8,8)); #定义腐蚀核形状
image_erodeedged = cv2.erode(image_dilateedged, errodekernel, iterations=1); #腐蚀

标记对象
因为图片中存在噪点,我们为了让对象查找更加精准,进行了对象筛选步骤。我们根据对象的面积来判断其是否是一个正常的目标点。

定义筛选函数
def contourfilter(contours,min_area,max_area,rate):
"""
contours 轮廓位置坐标数组
min_area 轮廓围成的区域的最小面积
max_area 轮廓围成的区域的最大面积
轮廓先要满足min_area 和 max_area 条件,然后需要满足包含轮廓的最小矩形的长宽比小于rate
"""
con = [];
for c in contours:
a = cv2.contourarea(c);
if((a > min_area) and (a < max_area)):
min_rect = cv2.minarearect(c);
#width 不一定比 height 小
width = min_rect[1][0];
height = min_rect[1][1];
if(not (width/height > rate or height/width > rate)):
con.append(c);
else:
continue;
return con;

寻找对象

contours, hierarchy = cv2.findcontours(image_erodeedged, cv2.retr_list, cv2.chain_approx_none);#cv2.findcountours 方法返回值的个数与cv2的版本有关。可能是2个也肯是3个,具体情况根据自身cv2版本调整
contours_filter = contourfilter(contours,3.5,50,2.5);
number = 0;
for cnt in contours_filter:
min_rect = cv2.minarearect(cnt);
min_rect = np.int0(cv2.boxpoints(min_rect));
cv2.drawcontours(image_contours,[min_rect],-1,(12,12,12),2);
number = number + 1;

从图中,我们可以看到,标记出的对象中存在一定的误差,边缘部分有噪点。用常规方法发现对象,在很大程度上依赖cv2.canny 方法,及以来cv2 库中寻找边界的方法。如果,边界能被精确识别,那么对象发现将会是零误差。因为我们后续操作都是建立在边界轮廓上。从图片的边界图中我们发现,边缘的边界识别存在很大的误差,出现多层边界。边界不是一条封闭的曲线。内部边界也有类似的情况。由于边界识别出现误差,导致在膨胀和腐蚀时边界会出现不规则的形状。甚至将正常点分化为噪点。为解决该问题,我们尝试了新方法;

界定边缘+膨胀+腐蚀

定义边界发现函数
def segment_on_dt(a, img):
border = cv2.dilate(img, none, iterations=5);
border = border - cv2.erode(border, none);

dt = cv2.distancetransform(img, 2, 3);
dt = ((dt - dt.min()) / (dt.max() - dt.min()) * 255).astype(np.uint8);
_, dt = cv2.threshold(dt, 180, 255, cv2.thresh_binary);
lbl, ncc = label(dt);
lbl = lbl * (255 / (ncc + 1));
# completing the markers now.
lbl[border == 255] = 255 ;

lbl = lbl.astype(np.int32);
print(lbl.shape);
cv2.watershed(a, lbl);

lbl[lbl == -1] = 0;
lbl = lbl.astype(np.uint8);
return 255 - lbl;

def fill(img,n=4):
"""
fill the edge
default 4 neighborhood
"""
neighbor = get_neighbor(n);
w,h = img.shape;
img_copy = img.copy();
for i in range(1,w-1):
for j in range(1,h-1):
if(img[i][j] == 255): #白色点
for ne in neighbor:
img_copy[i+ne[0],j+ne[1]] = 255;
return img_copy;

def get_neighbor(n):
if(n==4):
return [(-1,0),(1,0),(0,-1),(0,-1)];
if(n==8):
return [(-1,0),(1,0),(0,-1),(1,0),(-1,-1),(1,1),(-1,1),(1,-1)];
return [];

从结果上看,图片中存在区块色差。为此我们任然无法精确识别边界,该方法最终被我们放弃。新的方法总会产生,经过查阅资料,发现了一种通过遮罩模板的方法直接匹配目标点。这个方法简单直接,缺点就是我们需要选出许多遮罩模板,对于没有出现过的目标点则显得无能为力。其中需要解决的问题有两个,1是选出合适的遮罩模板;2是需要将每个遮罩模板匹配出的结果合并。

遮罩匹配

该方法主要使用到cv2.matchtemplate 方法,匹配度计算的标准有方差,相关系数。详细用法可以参考cv2.matchtemplate 教程

template_result = cv2.matchtemplate(image, mask, cv2.tm_ccoeff_normed);
loc = np.where(template_result >= 0.75);

从图中我们看见,使用遮罩模板选出的对象点的正确率基本达到100%。当然,这是在图片不复杂的情况下,如果图片存在噪点较多,目标点的颜色多样,那么这时我们必须增加遮罩模板的数量与种类。
总的来说,对象发现问题还有需要值得研究的地方。项目的源码请看github