# HG changeset patch # User Laman # Date 2019-01-13 17:54:48 # Node ID 92f4748d07b3d092eabcd36e6c5c24afa15a7ef9 # Parent 8be76da6645643bcde23efa0c31b9073b9b2ae45 exp: connecting segmented stones into lines vol 2 diff --git a/exp/stone_detect.py b/exp/stone_detect.py --- a/exp/stone_detect.py +++ b/exp/stone_detect.py @@ -3,7 +3,7 @@ sys.path.append("../src") import os import math -import heapq +import random import cv2 as cv import numpy as np @@ -14,6 +14,8 @@ from annotations import DataFile,compute from hough import show from analyzer.epoint import EPoint +random.seed(361) + class NeighboringPoint(EPoint): def __init__(self,x,y): @@ -47,7 +49,7 @@ def filterStones(contours,bwImg,stoneDim center=(moments["m10"]/(moments["m00"] or 1), moments["m01"]/(moments["m00"] or 1)) area=cv.contourArea(c) (x,y,w,h)=cv.boundingRect(c) - if w>stoneDims[0] or h>stoneDims[1]*1.3 or w<2 or h<2: + if w>stoneDims[0] or h>stoneDims[1]*1.5 or w<2 or h<2: cv.drawMarker(contourImg,tuple(map(int,center)),(0,0,255),cv.MARKER_TILTED_CROSS,12) keep=False coverage1=area/(w*h or 1) @@ -65,50 +67,27 @@ def filterStones(contours,bwImg,stoneDim return res -def computeDistances(points): - n=len(points) - res=np.zeros((n,n),dtype=np.float32) - - for (i,a) in enumerate(points): - for (j,b) in enumerate(points): - if j<i: continue - elif j==i: res[i,j]=0 - else: - res[i,j]=res[j,i]=a.dist(b) - - return res +def point2lineDistance(a,b,p): + # https://en.wikipedia.org/wiki/Point-line_distance#Line_defined_by_two_points + ab=b-a + num=abs(ab.y*p.x - ab.x*p.y + b.x*a.y - a.x*b.y) + denum=math.sqrt(ab.y**2+ab.x**2) + return num/denum # double_area / side_length == height -def collectNeighbours(points): - # we could do this in O(n log n) - # doi:10.1007/BF02187718 - # https://link.springer.com/content/pdf/10.1007/BF02187718.pdf - """:param points: [EPoint, ...] - :return: [(k_i,dist_i) for i in range(4)]""" - neighborhood=[NeighboringPoint(p.x,p.y) for p in points] - dists=computeDistances(points) - for (i,a) in enumerate(points): - neighbours=heapq.nsmallest(4,enumerate(dists[i]),key=lambda x: x[1] if x[1]>0 else 1000) - neighborhood[i].neighbours=[neighborhood[j] for (j,d) in neighbours] - return neighborhood - - -def growLine(r,s): - """:param r: NeighboringPoint - :param s: NeighboringPoint - :return: NeighboringPoint or None""" - u=s-r - alpha=math.atan2(u.y,u.x) - t=None - beta=alpha+math.pi # -alpha - for t_ in s.neighbours: - v=t_-s - beta_=math.atan2(v.y,v.x) # beta_ in (-pi, pi] - if abs(abs(alpha-beta_)-math.pi)>abs(abs(alpha-beta)-math.pi): - beta=beta_ - t=t_ - if abs(alpha-beta)<math.pi/12: return t - else: return None +def groupLines(points,minCount,tolerance): + random.shuffle(points) + sample=points[:57] + for (i,a) in enumerate(sample): + for (j,b) in enumerate(sample): + if j<=i: continue + incidentPoints=[a,b] + for c in points: + if c is a or c is b: continue + if point2lineDistance(a,b,c)<=tolerance: + incidentPoints.append(c) + if len(incidentPoints)>=minCount: + yield incidentPoints if __name__=="__main__": @@ -128,11 +107,12 @@ if __name__=="__main__": rect=img[y1:y2,x1:x2] unit=np.array([1,1,1],dtype=np.uint8) + kernel=np.ones((3,3),np.uint8) maskB=cv.inRange(rect,centers[0]-unit,centers[0]+unit) - maskB=cv.dilate(maskB,np.ones((3,3),np.uint8),iterations=1) - maskB=cv.erode(maskB,np.ones((3,3),np.uint8),iterations=3) + maskB=cv.morphologyEx(maskB,cv.MORPH_OPEN,kernel,iterations=1) + maskB=cv.erode(maskB,kernel,iterations=2) maskW=cv.inRange(rect,centers[1]-unit,centers[1]+unit) - maskW=cv.erode(maskW,np.ones((3,3),np.uint8),iterations=2) + maskW=cv.erode(maskW,kernel,iterations=2) show(img,filename) show(maskB,filename) @@ -145,24 +125,32 @@ if __name__=="__main__": (contours,hierarchy)=cv.findContours(stones,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE) stoneLocs=filterStones(contours,stones,stoneDims) - neighborhood=collectNeighbours([point for (point,contour) in stoneLocs]) linesImg=cv.cvtColor(np.zeros((h,w),np.uint8),cv.COLOR_GRAY2BGR) cv.drawContours(linesImg,[c for (point,c) in stoneLocs],-1,(255,255,255),-1) - for p in neighborhood: + for (p,c) in stoneLocs: cv.drawMarker(linesImg,(int(p.x),int(p.y)),(255,0,0),cv.MARKER_CROSS) - for p in neighborhood: - for q in p.neighbours: - length=1 - while True: - q_=growLine(p,q) - if q_: - q=q_ - length+=1 - else: break - if length>1 or True: - print(length,p,q) - cv.line(linesImg,(int(p.x),int(p.y)),(int(q.x),int(q.y)),(255,255,0),min((length+1)//2,3)) + lineSet=set() + minCount=min(max(math.sqrt(len(stoneLocs))-2,3),7) + print("min count:",minCount) + for points in groupLines([point for (point,contour) in stoneLocs],minCount,2): + points=tuple(sorted(tuple(p) for p in points)) + lineSet.add(points) + obsolete=set() + pointSet=set(points) + for line in lineSet: + lineS=set(line) + if points is line: continue + if pointSet<lineS: + lineSet.remove(points) + break + if lineS<pointSet: + obsolete.add(line) + lineSet-=obsolete + for line in sorted(lineSet,key=len,reverse=True)[:16]: + print(len(line),line) + (xa,ya)=line[0] + (xb,yb)=line[-1] + cv.line(linesImg,(int(xa),int(ya)),((int(xb),int(yb))),(255,255,0),1) show(linesImg) - cv.imwrite("/tmp/img.png",linesImg) diff --git a/src/analyzer/epoint.py b/src/analyzer/epoint.py --- a/src/analyzer/epoint.py +++ b/src/analyzer/epoint.py @@ -64,5 +64,10 @@ class EPoint: def __neg__(self): return EPoint(-self.x,-self.y) + def __getitem__(self,key): + if key==0: return self.x + elif key==1: return self.y + raise IndexError(key) + def __str__(self): return "({0},{1})".format(round(self.x,3),round(self.y,3)) def __repr__(self): return "EPoint({0},{1})".format(round(self.x,3),round(self.y,3))