在工程项目中遇到的两个实际问题,因为没有现成的函数解决问题,所以想了一些方法来完成,并不是很完美的解决办法,但是可以近似的完成任务,记录一下解决思路。
问题一:一张图像,里面打了一堆点,要求对点做一个筛选:任意一个点,以半径为threshold画圆,如果在此范围内没有其他的点存在,那么就认为这个点是孤立的点。要求将图中的点分为两类:孤立的点,不孤立的点。
解决思路:最朴素的做法是来个双重循环,计算出任意一个点和图中所有点的距离,如果某个点和所有点的距离都大于半径,那他就是孤立的点。但是显然,这种做法复杂度为O(n^2),有没有更简便的做法呢?
我没有查找到专门进行此操作的函数,所以就自己想了个方法
既然我们想知道以这个点为中心的一定范围内有没有别的点,那并不一定要画圆,我的做法是画一个正方形,准确来说是画一个目标圆的外切正方形。我们创建一个与目标图像等高等宽的全黑色的图像,然后把目标点全部画上去,画成白色,然后呢,遍历每一个点,取以该点为圆心,threshold为半径的圆的外切正方形,(为什么这么做,因为取圆不好取,取正方形直接对图像切片即可,非常方便),将这块正方形裁剪出来,OpenCv有一个函数,cv2.countNonZero可以获得图像中不为0的像素数量,用这个函数,我们就可以轻松的知道该点周围有没有别的点,它是不是孤立的点。
此方法的复杂度差不多是O(n)
# vib_pts:[(x,y),(x,y),....] 是点的集合
# shape是图像大小
# threshold 是筛选范围
def distance_filter(vib_pts,shape,threshold):
ans = [[],[]] # 距离合格的是ans[0] 不合格的是ans[1]
# 行数 列数 通道数
img = np.zeros((shape[0],shape[1],1),dtype=np.uint8)
for i in vib_pts:
img[ i[0],i[1] ] = 1
for i in vib_pts:
# 裁剪不能超出范围
x1 = max(i[0] - threshold,0) # left
x2 = min(i[0] + threshold,shape[1]) # right
y1 = max(i[1] - threshold,0) # up
y2 = min(i[1] + threshold,shape[0]) # down
temp_img = img[y1:y2,x1:x2]
cnt = cv2.countNonZero(temp_img)
if cnt > 1: # 距离合格
ans[0].append(i)
else: # 是一个孤立的点 (距离不合格)
ans[1].append(i)
return ans
问题二
工程中对一个管状物体做了识别,识别结果如下,左边是对该物体的语义分割结果,右边是用opencv画出的轮廓。
(注意,左图是处理过的二值图,只有黑白两色)
我们的目的是想求出该物体的平均宽度(像素宽度)。
这样一个图,使用opencv提供的寻找轮廓的函数findContours可以获得轮廓点,也可以获得面积,周长。
如何求宽度呢?
刚开始有个思路是随机的选取一个轮廓点,然后拿这个点,向左向右搜索,再向上向下搜索(这个物体不一定是竖着的,也可能横着),分别搜索出它的白色宽度,然后纵向宽度和横向宽度取个最小值,重复随机计算30次,取个平均值作为物体宽度,实验发现,这个结果不是很准,误差较大。
又想了一个方法
我把问题抽象一下,既然有周长有宽度,那么我不管这个曲曲折折的物体是什么样子,我直接计算一个同样周长和面积的长方形,(相当于把这个管状物体“抻直”,近似成一个长方形),那么问题就转化为:已知某个长方形的面积和周长,求出长和宽?
那就简单了,假设面积=S,周长=P,长和宽=x,y
x + y = P / 2
x * y = S
把 y = S / x 代入第一个式子,即可得到 x^2 - (P/2)x + S = 0
使用求根公式即可求出x,一元二次方程嘛,求出来两个值,取最小的
# ret,binary = cv2.threshold(img1,1,255,cv2.THRESH_BINARY) # 二值化处理
# cnts, hierarchy = cv2.findContours(binary,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) #轮廓搜索
# cnts是轮廓搜索函数返回的轮廓点集合(有多个轮廓)
def average_width(cnts):
if len(cnts) == 0: return 0
average_width = 0
for cnt in cnts: # 每个轮廓都计算
# 计算轮廓所包含的面积
S = cv2.contourArea(cnt)
# 计算轮廓的周长
P = cv2.arcLength(cnt, True)
# 假设长和宽为x y 周长为P 面积为 S 有关系 x^2 - (P/2)x + S = 0 x + y = P/2
# 根据求根公式 x = ( -b +- sqrt(b^2 - 4ac) )/(2a) 其中 a=1 b=-(P/2) c=S
# 解出一元二次方程的两个解
c1 = int((P / 2 + ((P / 2) ** 2 - 4 * S) ** 0.5) / 2)
c2 = int((P / 2 - ((P / 2) ** 2 - 4 * S) ** 0.5) / 2)
c_x = max(c1, c2) # 取最大的那个值 作为长或者宽
c_y = int((P/2) - c_x) # 求出另一个边长
average_width += min(c_x,c_y) # 取最小的那个为宽度
return int(average_width/len(cnts)) # 返回均值



