Changeset - 92f4748d07b3
[Not reviewed]
default
0 2 0
Laman - 6 years ago 2019-01-13 17:54:48

exp: connecting segmented stones into lines vol 2
2 files changed with 55 insertions and 62 deletions:
0 comments (0 inline, 0 general)
exp/stone_detect.py
Show inline comments
 
import sys
 
sys.path.append("../src")
 

	
 
import os
 
import math
 
import heapq
 
import random
 

	
 
import cv2 as cv
 
import numpy as np
 
import scipy.cluster
 
import scipy.ndimage
 

	
 
from annotations import DataFile,computeBoundingBox
 
from hough import show
 
from analyzer.epoint import EPoint
 

	
 
random.seed(361)
 

	
 

	
 
class NeighboringPoint(EPoint):
 
	def __init__(self,x,y):
 
		super().__init__(x,y)
 
		self.neighbours=[]
 

	
 

	
 
def kmeans(img):
 
	arr=np.reshape(img,(-1,3)).astype(np.float)
 
	colors=np.array([[0,0,0],[255,255,255],[193,165,116]],np.float)
 
	print(colors)
 
	(centers,distortion)=scipy.cluster.vq.kmeans(arr,colors)
 
	print("k-means centers:",centers)
 
	return centers
 

	
 

	
 
def quantize(img,centers):
 
	origShape=img.shape
 
	data=np.reshape(img,(-1,3))
 
	(keys,dists)=scipy.cluster.vq.vq(data,centers)
 
	pixels=np.array([centers[k] for k in keys],dtype=np.uint8).reshape(origShape)
 
	return pixels
 

	
 

	
 
def filterStones(contours,bwImg,stoneDims):
 
	contourImg=cv.cvtColor(bwImg,cv.COLOR_GRAY2BGR)
 
	res=[]
 
	for (i,c) in enumerate(contours):
 
		keep=True
 
		moments=cv.moments(c)
 
		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)
 
		hull=cv.convexHull(c)
 
		coverage2=area/(cv.contourArea(hull) or 1)
 
		if coverage2<0.8:
 
			cv.drawMarker(contourImg,tuple(map(int,center)),(0,127,255),cv.MARKER_DIAMOND,12)
 
			keep=False
 
		if keep:
 
			res.append((EPoint(*center),c))
 
			cv.drawMarker(contourImg,tuple(map(int,center)),(255,0,0),cv.MARKER_CROSS)
 
	print("accepted:",len(res))
 
	print("rejected:",len(contours)-len(res))
 
	show(contourImg)
 
	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__":
 
	filepath=sys.argv[1]
 
	annotations=DataFile(sys.argv[2])
 
	filename=os.path.basename(filepath)
 
	(x1,y1,x2,y2)=computeBoundingBox(annotations[filename][0])
 
	(w,h)=(x2-x1,y2-y1)
 
	img=cv.imread(filepath)
 
	(x3,x4,y3,y4)=(x1+w//4,x1+3*w//4,y1+h//4,y1+3*h//4)
 
	print("x3,x4,y3,y4:",x3,x4,y3,y4)
 
	rect=img[y3:y4,x3:x4,:]
 
	centers=kmeans(rect)
 
	print("x1,x2,y1,y2:",(x1,x2,y1,y2))
 
	img[y1:y2,x1:x2,:]=quantize(img[y1:y2,x1:x2,:],centers)
 
	print("image quantized")
 

	
 
	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)
 
	show(maskW,filename)
 
	stones=cv.bitwise_or(maskB,maskW)
 
	show(stones)
 

	
 
	stoneDims=(w/19,h/19)
 
	print("stone dims:",tuple(x/2 for x in stoneDims),"-",stoneDims)
 

	
 
	(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)
src/analyzer/epoint.py
Show inline comments
 
import math
 

	
 

	
 
## Euclidean 2D plane point: (x,y).
 
class EPoint:
 
	def __init__(self,x,y):
 
		self.x=x
 
		self.y=y
 

	
 
	@staticmethod
 
	def fromProjective(point):
 
		if point.item(0)==0: return None
 
		return EPoint(point.item(1)/point.item(0),point.item(2)/point.item(0))
 

	
 
	def toProjective(self):
 
		return (1,self.x,self.y)
 

	
 
	def dist(self,a):
 
		return math.sqrt((self.x-a.x)**2+(self.y-a.y)**2)
 

	
 
	def __add__(self,a):
 
		return EPoint(self.x+a.x,self.y+a.y)
 

	
 
	def __sub__(self,a):
 
		return EPoint(self.x-a.x,self.y-a.y)
 

	
 
	def __mul__(self,k):
 
		return EPoint(self.x*k,self.y*k)
 

	
 
	def __rmul__(self,k):
 
		return self*k
 

	
 
	def __truediv__(self,k):
 
		return EPoint(self.x/k,self.y/k)
 

	
 
	def __floordiv__(self,k):
 
		return EPoint(self.x//k,self.y//k)
 

	
 
	def __iadd__(self,a):
 
		self.x+=a.x
 
		self.y+=a.y
 
		return self
 

	
 
	def __isub__(self,a):
 
		self.x-=a.x
 
		self.y-=a.y
 
		return self
 

	
 
	def __imul__(self,k):
 
		self.x*=k
 
		self.y*=k
 
		return self
 

	
 
	def __itruediv__(self,k):
 
		self.x/=k
 
		self.y/=k
 
		return self
 

	
 
	def __ifloordiv__(self,k):
 
		self.x//=k
 
		self.y//=k
 
		return self
 

	
 
	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))
0 comments (0 inline, 0 general)