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