import sys sys.path.append("../src") import math import random from datetime import datetime import os.path import logging as log import numpy as np import scipy.optimize import scipy.signal import cv2 as cv import config as cfg from geometry import EPoint,Line DEBUG=True class LineBag: def __init__(self): self._lines=[] def put(self,score,alpha,beta,peaks): self._lines.append((score,alpha,beta,peaks)) def pull(self,count): self._lines.sort(reverse=True) res=[] for (score,alpha,beta,peaks) in self._lines: if any(abs(alpha-gamma)<10 and abs(beta-delta)<10 for (_,gamma,delta,_) in res): continue # avoid intersecting lines if any((beta-delta)!=0 and (alpha-gamma)/(beta-delta)<0 for (_,gamma,delta,_) in res): continue res.append((score,alpha,beta,peaks)) if len(res)>=count: break return res class HoughTransform: """Find line sequences with Hough transform. Uses usual image coordinates on input and output, with [0,0] in the upper left corner and [height-1,width-1] in the lower right. However, internally it uses the usual cartesian coordinates, centered at the image center. [-w/2,-h/2] in the upper left and [w/2,h/2] in the lower right.""" def __init__(self,img): self._angleBandwidth=30 # degrees (h,w)=img.shape[:2] self._diagLen=int(np.sqrt(h**2+w**2))+1 self._center=(w//2,h//2) self._acc=np.zeros((180,self._diagLen),dtype=np.int32) self.update(img) def extract(self): img=self._createImg() self.show(img) lines=self._detectLines() res=[] i=0 for (score,alpha,beta,peaks) in lines: log.debug("score: %s",score) log.debug("alpha, beta: %s, %s",alpha,beta) self._drawLine(img,alpha,beta,peaks,i) res.append([]) keys=self._readLineKeys(alpha,beta) for k in peaks: (alphaDeg,d)=keys[k] line=Line(alphaDeg*math.pi/180,d-self._diagLen//2) res[-1].append(self._transformOutput(line)) res[-1].sort(key=lambda line: line.d) i+=1 self.show(img) return res def update(self,img,weight=1): start=datetime.now().timestamp() for (r,row) in enumerate(img): for (c,pix) in enumerate(row): if pix==0: continue for alphaDeg in range(0,180): d=self._computeDist(c,r,alphaDeg)+self._diagLen//2 self._acc[(alphaDeg,d)]+=weight log.debug("Hough updated in %s s",round(datetime.now().timestamp()-start,3)) def scoreLine(self,line): transformed=self._transformInput(line) alphaDeg=round(transformed.alpha*180/math.pi)%180 d=round(transformed.d+self._diagLen//2) if not 0<=d=180: k=k%180 i=n-i res.append((k,i)) return res def _transformInput(self,line): reflectedLine=Line(math.pi*2-line.alpha,line.d) (x,y)=self._center basis=EPoint(x,-y) shiftedLine=reflectedLine.shiftBasis(basis) if shiftedLine.alpha>=math.pi: shiftedLine=Line(shiftedLine.alpha-math.pi,-shiftedLine.d) return shiftedLine def _transformOutput(self,line): (x,y)=self._center basis=EPoint(-x,y) shiftedLine=line.shiftBasis(basis) reflectedLine=Line(math.pi*2-shiftedLine.alpha,shiftedLine.d) log.debug("%s -> %s",line,reflectedLine) return reflectedLine def _createImg(self): maxVal=self._acc.max() arr=np.expand_dims(np.uint8(255*self._acc//maxVal),axis=2) img=np.concatenate((arr,arr,arr),axis=2) (h,w)=img.shape[:2] for x in range(0,w,4): # y axis img[h//2,x]=[255,255,255] for y in range(0,h,4): img[y,w//2]=[255,255,255] return img def _drawLine(self,img,alpha,beta,peaks,colorKey): colors=[[0,255,255],[255,0,255],[255,255,0]] color=colors[colorKey] (h,w)=img.shape[:2] keys=self._readLineKeys(alpha,beta) for (y,x) in keys: if x%3!=0: continue if y<0 or y>=h: continue img[y,x]=color for k in peaks: (y,x)=keys[k] cv.drawMarker(img,(x,y),color,cv.MARKER_TILTED_CROSS,8) class Accumulator: NEIGHBOURHOOD=2 def __init__(self): self._acc=[] self._hits=[] def add(self,line): (d,k)=self._findClosest(line) if d<=self.NEIGHBOURHOOD: self._acc=self._averageLines(self._acc[k],line,self._hits[k],1) self._hits[k]+=1 else: k=-1 self._acc.append(line) self._hits.append(1) return (self._hits[k],k) def pop(self,k): acc=self._acc (acc[k],acc[-1])=(acc[-1],acc[k]) hits=self._hits (hits[k],hits[-1])=(hits[-1],hits[k]) hits.pop() return acc.pop() def _findClosest(self,line): def dist(p,q): alpha=p.alpha*180/math.pi beta=q.alpha*180/math.pi gamma=abs(alpha-beta) if gamma>180: gamma=360-gamma return math.sqrt(gamma**2+(p.d-q.d)**2) (d,key)=min(zip((dist(line,p) for p in self._acc), range(len(self._acc)))) return (d,key) def _averageLines(self,ab,cd,w1,w2): w=w1+w2 (a,b)=ab.toPoints() (c,d)=cd.toPoints() e=(a*w1+c*w2)/w f=(b*w1+c*w2)/w return Line.fromPoints(e,f) class RandomizedHoughTransform: HIT_LIMIT=10 CANDIDATE_LIMIT=10 MIN_SCORE=50 def __init__(self,img): self._img=np.copy(img) (self._h,self._w)=img.shape[:2] self._acc=Accumulator() self._candidates=[] self._res=[] def _sampleLine(self): """:return: (Line) p""" a=self._chooseRandomPixel() b=self._chooseRandomPixel() while b==a: b=self._chooseRandomPixel() return Line.fromPoints(a,b) def _updateAcc(self,line): (hits,k)=self._acc.add(line) if hits>=self.HIT_LIMIT: self._addCandidate(self._acc.pop(k)) def _addCandidate(self,line): self._candidates.append(line) if len(self._candidates)>=self.CANDIDATE_LIMIT: for p in self._candidates: p_=self._confirmLine(p) if p_: self._res.append(p_) self._candidates=[] def _chooseRandomPixel(self): val=0 while not val: x=random.randrange(0,self._w) y=random.randrange(0,self._h) val=self._img[y,x] return EPoint(x,y) def _confirmLine(self,line): score=0 for point in self._walkLine(line): if self._img[point]==1: score+=1 if score>self.MIN_SCORE: for point in self._walkLine(line): # erase the line self._img[point]=0 return line else: return None def _walkLine(self,line): (a,b,c)=line.toNormal() if abs(line.alpha-math.pi/2)