对象发现

发布时间:2019-05-20 22:58:24编辑:auto阅读(1946)

    对象发现

    不知道什么原因博客园在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

关键字