一、前言
本篇主要是针对背景简单,且具有固定颜色的单类小物体,方法为在灰度化时选用图片的HSV中的S通道,再使用opencv 二值化找轮廓大法可将小物体框出。
原理很简单,图片-》取S通道灰度化-》OTSU二值化-》findcontours找到轮廓
二、代码
import cv2
import numpy as np
import imutils
IDX = 0 #选择第几张图片
def process( img ):
img_show = img.copy()
#做灰度化,颗粒具有特定的颜色,转为HSV,取S通道作为灰度图像
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
gray = img_hsv[ :,:,1 ]
#采用OTSU 自动阈值二值化做二值化,颗粒部分为白色255,背景为黑色0
_, th = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
#开运算,被定义为先腐蚀后膨胀,作用是去掉一些白色噪声点,防止将白色噪声点框出
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) # 矩形结构
binary = cv2.morphologyEx(th, cv2.MORPH_OPEN, kernel) #开运算
#找轮廓大法,即找到颗粒部分,也即白色部分的最小外接矩形,带方向角
cnts = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#cnts = cnts[1] if imutils.is_cv3() else cnts[0]
cnts = cnts[0] #
particle = []
all_area , all_v = 0. , 0.
if IDX == 0: #图片字体大小设置,第0张图的分辨率小,所以字体要小些,后2张字体大些
ws = 0.8
else:
ws = 1.6
x_thr , y_thr = 10, 10 #边框的x或y长度不能小于这2个阈值,滤除一些小噪点干扰
if len(cnts) > 0:
c = 1 #颗粒序号
for cnt in cnts : #开始遍历找到的所有白色边框
x_coord = cnt[:, :, 0]
y_coord = cnt[:, :, 1]
xmin = int(np.min(x_coord))
xmax = int(np.max(x_coord))
ymin = int(np.min(y_coord))
ymax = int(np.max(y_coord))
rect = cv2.minAreaRect(cnt) #最小外接矩形,带方向角的
box = cv2.cv.Boxpoints() if imutils.is_cv2()else cv2.boxPoints(rect)
box = np.int0(box)
# 两条边有任何一条是小于10像素的,都不算是颗粒,不做统计
if np.abs( xmax-xmin ) < x_thr or np.abs( ymax-ymin ) <y_thr:continue
#最小外接矩形,画出4条边
color =[(255,0,0),(0,255,0),(0,0,255),(0,0,0)]
for i in range(3):
cv2.line(img_show,tuple(box[i]),tuple( box[i+1] ),color[i],3)
cv2.line(img_show,tuple(box[3]),tuple( box[0] ),color[3],3)
#需要获取这4个点的长边和短边
#很简单,只需计算4条边,各有2条边长度是差不多,取他们的平均值,就能获得长边和短边
lines = []
for i in range(3):
lines.append( np.sqrt( ( box[i][0]-box[i+1][0] )**2+( box[i][1]-box[i+1][1] )**2 ) )
lines.append( np.sqrt( ( box[3][0]-box[0][0] )**2+( box[3][1]-box[0][1] )**2 ) )
lines_sort = np.sort(lines) #排序,前2个是短边,后2个是长边,取平均
s = ( lines_sort[0]+lines_sort[1] )/2 #短边,取平均值
#h = ( lines_sort[1]+lines_sort[2] )/2 #长边,取平均值
h = ( lines_sort[2]+lines_sort[3] )/2 #长边,取平均值
#计算面积,体积
area = s*h
v = np.pi*(s**2)*h
all_area+=area
all_v+=v
particle.append( [ area,v ] ) #每个颗粒的面积和体积保存于list
x , y = int((xmin+xmax)/2) , int((ymin+ymax)/2) #在图上画出每个颗粒的序号
cv2.putText(img_show, "%d"%c, ( xmin ,y ), 0, ws, (255, 0, 255), 2 )
print("颗粒序号:" , c , "面积:" , area ,"体积:", v )
c+=1 #颗粒序号自加1
n = len( particle )
print("颗粒数:", n , "总面积:",all_area , "总体积:", all_v )
str_0 = "num:%d all_area:%.2f all_volume:%.2f"%( int(n) , all_area , all_v)
cv2.putText(img_show, str_0 , (10,200), 0, ws, (255, 0, 255), 2 )
cv2.namedWindow("img" , 2)
cv2.imshow("img" , img_show )
cv2.namedWindow("th" , 2)
cv2.imshow("th" , th)
cv2.namedWindow("binary" , 2)
cv2.imshow("binary" , binary)
if __name__ == "__main__":
pic_name =[ "1.jpg" , "2.bmp" , "3.bmp" ,"4.bmp" ]
if IDX in [ 0,1,2 ,3]:
img = cv2.imread( pic_name[ IDX ] )
process( img )
cv2.waitKey()
cv2.destroyAllWindows()
三、结果
可以看到,对于这种背景简单的,取HSV中的S做灰度化二值化,效果非常不错,再用找轮廓大法,可以获得带方向的最小外接矩形,故可将每条边找到,或者框起来...