How to fix incorrect energy conservation problem in mass-spring-system simulation using RK4 method - simulation

I am making a simulation where you create different balls of certain mass, connected by springs which you can define (in the program below all springs have natural length L and spring constant k). How I do it is I created a function accel(b,BALLS), (note b is the specific ball and BALLS are all of the ball objects in various stages of update) which gets me acceleration on this one ball from calculating all the forces acting on it (tensions from ball the springs connected to it and gravity) and I would think this function is definitely correct and problems lie elsewhere in the while loop. I then use the RK4 method described on this website: http://spiff.rit.edu/richmond/nbody/OrbitRungeKutta4.pdf in the while loop to update velocity and position of each ball. To test my understanding of the method I first made a simulation where only two balls and one spring is involved on Desmos: https://www.desmos.com/calculator/4ag5gkerag
I allowed for energy display and saw that indeed RK4 is much better than Euler method. Now I made it in python in the hope that it should work with arbitrary config of balls and springs, but energy isn't even conserved when I have two balls and one spring! I couldn't see what I did differently, at least when two balls on involved. And when I introduce a third ball and a second spring to the system, energy increases by the hundreds every second. This is my first time coding a simulation with RK4, and I expect you guys can find mistakes in it. I have an idea that maybe the problem is caused by because there are multiple bodies and difficulties arises when I update their kas or kvs at the same time but then again I can't spot any difference between what this code is doing when simulating two balls and my method used in the Desmos file. Here is my code in python:
import pygame
import sys
import math
import numpy as np
pygame.init()
width = 1200
height = 900
SCREEN = pygame.display.set_mode((width, height))
font = pygame.font.Font(None, 25)
TIME = pygame.time.Clock()
dampwall = 1
dt = 0.003
g = 20
k=10
L=200
def dist(a, b):
return math.sqrt((a[0] - b[0])*(a[0] - b[0]) + (a[1] - b[1])*(a[1] - b[1]))
def mag(a):
return dist(a, [0, 0])
def dp(a, b):
return a[0]*b[0]+a[1]*b[1]
def norm(a):
return list(np.array(a)/mag(a))
def reflect(a, b):
return norm([2*a[1]*b[0]*b[1]+a[0]*(b[0]**2 - b[1]**2), 2*a[0]*b[0]*b[1]+a[1]*(-b[0]**2 + b[1]**2)])
class ball:
def __init__(self, x, y, vx, vy, mass,spr,index,ka,kv):
self.r = [x, y]
self.v = [vx, vy]
self.radius = 5
self.mass = mass
self.spr=spr
self.index = index
self.ka=ka
self.kv=kv
def detectbounce(self,width,height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0] or self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0] or self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1] or self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
return True
def bounce_walls(self, width, height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0]:
self.v[0] *= -dampwall
if self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0]:
self.v[0] *= -dampwall
if self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1]:
self.v[1] *= -dampwall
if self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
self.v[1] *= -dampwall
def update_r(self,v, h):
self.r[0] += v[0] * h
self.r[1] += v[1] * h
def un_update_r(self,v, h):
self.r[0] += -v[0] * h
self.r[1] += -v[1] * h
def KE(self):
return 0.5 * self.mass * mag(self.v)**2
def GPE(self):
return self.mass * g * (-self.r[1] + height)
def draw(self, screen, width, height):
pygame.draw.circle(screen, (0, 0, 255), (self.r[0] +
width / 2, self.r[1] + height / 2), self.radius)
#(self, x, y, vx, vy, mass,spr,index,ka,kv):
# balls = [ball(1, 19, 0, 0,5,[1],0,[0,0,0,0],[0,0,0,0]), ball(250, 20, 0,0,1,[0],1,[0,0,0,0],[0,0,0,0])]
# springs = [[0, 1]]
balls = [ball(1, 19, 0, 0,5,[1,3],0,[0,0,0,0],[0,0,0,0]), ball(250, 20, 0,0,2,[0,2,3],1,[0,0,0,0],[0,0,0,0]),ball(450, 0, 0,0,2,[1,3],1,[0,0,0,0],[0,0,0,0]),ball(250, -60, 0,0,2,[0,1,2],1,[0,0,0,0],[0,0,0,0])]
springs = [[0, 1],[1,2],[0,3],[1,3],[2,3]]
def accel(b,BALLS):
A=[0,g]
for i in range(0,len(b.spr)):
ball1=b
ball2=BALLS[b.spr[i]]
r1 = norm(list(np.array(ball2.r) - np.array(ball1.r)))
lnow = dist(ball1.r, ball2.r)
force = k * (lnow - L)
A[0]+=force/ball1.mass*r1[0]
A[1]+=force/ball1.mass*r1[1]
return A
initE=0
while True:
TIME.tick(200)
SCREEN.fill((0, 0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#compute k1a and k1v for all balls
for ball in balls:
ball.ka[0]=accel(ball,balls)
ball.kv[0]=ball.v
#create newb1 based on 'updated' position of all balls with their own k1v
newb=[]
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
newb.append(ball)
ball.un_update_r(ball.kv[0], dt/2)
#compute k2a and k2v for all balls based on newb1
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
ball.ka[1]=accel(ball,newb)
ball.un_update_r(ball.kv[0], dt/2)
ball.kv[1]=[ball.v[0]+0.5*dt*ball.ka[0][0],ball.v[1]+0.5*dt*ball.ka[0][1]]
#create newb2 based on 'updated' position of all balls with their own k2v
newb=[]
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
newb.append(ball)
ball.un_update_r(ball.kv[1], dt/2)
#compute k3a and k3v for all balls
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
ball.ka[2]=accel(ball,newb)
ball.un_update_r(ball.kv[1], dt/2)
ball.kv[2]=[ball.v[0]+0.5*dt*ball.ka[1][0],ball.v[1]+0.5*dt*ball.ka[1][1]]
newb=[]
for ball in balls:
ball.update_r(ball.kv[2], dt)
newb.append(ball)
ball.un_update_r(ball.kv[2], dt)
#compute k4a and k4v for all balls
for ball in balls:
ball.update_r(ball.kv[2], dt)
ball.ka[3]=accel(ball,newb)
ball.un_update_r(ball.kv[2], dt)
ball.kv[3]=[ball.v[0]+dt*ball.ka[2][0],ball.v[1]+dt*ball.ka[2][1]]
#final stage of update
for ball in balls:
if ball.detectbounce(width,height)==True:
ball.bounce_walls(width, height)
else:
ball.v=[ball.v[0]+dt*(ball.ka[0][0]+2*ball.ka[1][0]+2*ball.ka[2][0]+ball.ka[3][0])/6, ball.v[1]+dt*(ball.ka[0][1]+2*ball.ka[1][1]+2*ball.ka[2][1]+ball.ka[3][1])/6]
ball.r=[ball.r[0]+dt*(ball.kv[0][0]+2*ball.kv[1][0]+2*ball.kv[2][0]+ball.kv[3][0])/6, ball.r[1]+dt*(ball.kv[0][1]+2*ball.kv[1][1]+2*ball.kv[2][1]+ball.kv[3][1])/6]
for ball in balls:
ball.draw(SCREEN, width, height)
for i in range(0,len(ball.spr)):
ball1=ball
ball2=balls[ball.spr[i]]
pygame.draw.line(SCREEN, (0, 0, 155), (
ball1.r[0]+width/2, ball1.r[1]+height/2), (ball2.r[0]+width/2, ball2.r[1]+height/2))
#check for energy
KE = 0
EPE = 0
GPE = 0
for i in range(0, len(springs)):
EPE += 1/2 * k * \
(L - dist(balls[springs[i][0]].r,
balls[springs[i][1]].r))**2
for i in range(0, len(balls)):
KE += balls[i].KE()
GPE += balls[i].GPE()
if initE == 0:
initE += KE+EPE+GPE
text = font.render('init Energy: ' + str(round(initE,1))+' '+'KE: ' + str(round(KE, 1)) + ' '+'EPE: ' + str(round(EPE, 1))+' ' + 'GPE: ' + str(round(GPE, 1)) + ' ' + 'Total: ' + str(round(KE+EPE+GPE, 1)) + ' ' + 'Diff: ' + str(round((KE+EPE+GPE-initE), 1)),
True, (255, 255, 255))
textRect = text.get_rect()
textRect.center = (370, 70)
SCREEN.blit(text, textRect)
pygame.display.flip()
This is the edited, corrected by Lutz Lehmann and with some extra improvements:
import pygame
import sys
import math
import numpy as np
pygame.init()
width = 1200
height = 900
SCREEN = pygame.display.set_mode((width, height))
font = pygame.font.Font(None, 25)
TIME = pygame.time.Clock()
dampwall = 1
dt = 0.003
g = 5
k = 10
L = 200
digits = 6
def dist(a, b):
return math.sqrt((a[0] - b[0])*(a[0] - b[0]) + (a[1] - b[1])*(a[1] - b[1]))
def mag(a):
return dist(a, [0, 0])
def dp(a, b):
return a[0]*b[0]+a[1]*b[1]
def norm(a):
return list(np.array(a)/mag(a))
def reflect(a, b):
return norm([2*a[1]*b[0]*b[1]+a[0]*(b[0]**2 - b[1]**2), 2*a[0]*b[0]*b[1]+a[1]*(-b[0]**2 + b[1]**2)])
class Ball:
def __init__(self, x, y, vx, vy, mass, spr, index, ka, kv):
self.r = [x, y]
self.v = [vx, vy]
self.radius = 5
self.mass = mass
self.spr = spr
self.index = index
self.ka = ka
self.kv = kv
def copy(self):
return Ball(self.r[0], self.r[1], self.v[0], self.v[1], self.mass, self.spr, self.index, self.ka, self.kv)
def detectbounce(self, width, height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0] or self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0] or self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1] or self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
return True
def bounce_walls(self, width, height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0]:
self.v[0] *= -dampwall
if self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0]:
self.v[0] *= -dampwall
if self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1]:
self.v[1] *= -dampwall
if self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
self.v[1] *= -dampwall
def update_r(self, v, h):
self.r[0] += v[0] * h
self.r[1] += v[1] * h
def un_update_r(self, v, h):
self.r[0] += -v[0] * h
self.r[1] += -v[1] * h
def KE(self):
return 0.5 * self.mass * mag(self.v)**2
def GPE(self):
return self.mass * g * (-self.r[1] + height)
def draw(self, screen, width, height):
pygame.draw.circle(screen, (0, 0, 255), (self.r[0] +
width / 2, self.r[1] + height / 2), self.radius)
# (self, x, y, vx, vy, mass,spr,index,ka,kv):
# balls = [Ball(1, 19, 0, 0, 1, [1], 0, [0, 0, 0, 0], [0, 0, 0, 0]),
# Ball(250, 20, 0, 0, 1, [0], 1, [0, 0, 0, 0], [0, 0, 0, 0])]
# springs = [[0, 1]]
balls = [Ball(1, 19, 0, 0,5,[1,3],0,[0,0,0,0],[0,0,0,0]), Ball(250, 20, 0,0,2,[0,2,3],1,[0,0,0,0],[0,0,0,0]),Ball(450, 0, 0,0,2,[1,3],1,[0,0,0,0],[0,0,0,0]),Ball(250, -60, 0,0,2,[0,1,2],1,[0,0,0,0],[0,0,0,0])]
# n=5
# resprings=[]
# for i in range(0,n):
# for j in range(0,n):
# if i==0 and j==0:
# resprings.append([1,2,n,n+1,2*n])
# if i==n and j==0:
# resprings.apend([n*(n-1)+1,n*(n-1)+2,n*(n-2),n*(n-3),n*(n-2)+1])
# if j==0 and i!=0 or i!=n:
# resprings.append([(i-1)*n+1,(i-1)*n+2,(i-2)*n,(i-2)*n+1,(i)*n,(i)*n+1])
def getsprings(B):
S=[]
for i in range(0,len(B)):
theball=B[i]
for j in range(len(theball.spr)):
spring=sorted([i,theball.spr[j]])
if spring not in S:
S.append(spring)
return S
springs = getsprings(balls)
def accel(b, BALLS):
A = [0, g]
for i in range(0, len(b.spr)):
ball1 = b
ball2 = BALLS[b.spr[i]]
r1 = norm(list(np.array(ball2.r) - np.array(ball1.r)))
lnow = dist(ball1.r, ball2.r)
force = k * (lnow - L)
A[0] += force/ball1.mass*r1[0]
A[1] += force/ball1.mass*r1[1]
return A
initE = 0
while True:
TIME.tick(200)
SCREEN.fill((0, 0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
for ball in balls:
ball.bounce_walls(width, height)
# compute k1a and k1v for all balls
for ball in balls:
ball.ka[0] = accel(ball, balls)
ball.kv[0] = ball.v
# create newb1 based on 'updated' position of all balls with their own k1v
newb = []
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
newb.append(ball.copy())
ball.un_update_r(ball.kv[0], dt/2)
# compute k2a and k2v for all balls based on newb1
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
ball.ka[1] = accel(ball, newb)
ball.un_update_r(ball.kv[0], dt/2)
ball.kv[1] = [ball.v[0]+0.5*dt*ball.ka[0]
[0], ball.v[1]+0.5*dt*ball.ka[0][1]]
# create newb2 based on 'updated' position of all balls with their own k2v
newb = []
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
newb.append(ball.copy())
ball.un_update_r(ball.kv[1], dt/2)
# compute k3a and k3v for all balls
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
ball.ka[2] = accel(ball, newb)
ball.un_update_r(ball.kv[1], dt/2)
ball.kv[2] = [ball.v[0]+0.5*dt*ball.ka[1]
[0], ball.v[1]+0.5*dt*ball.ka[1][1]]
newb = []
for ball in balls:
ball.update_r(ball.kv[2], dt)
newb.append(ball.copy())
ball.un_update_r(ball.kv[2], dt)
# compute k4a and k4v for all balls
for ball in balls:
ball.update_r(ball.kv[2], dt)
ball.ka[3] = accel(ball, newb)
ball.un_update_r(ball.kv[2], dt)
ball.kv[3] = [ball.v[0]+dt*ball.ka[2][0], ball.v[1]+dt*ball.ka[2][1]]
# final stage of update
for ball in balls:
ball.v = [ball.v[0]+dt*(ball.ka[0][0]+2*ball.ka[1][0]+2*ball.ka[2][0]+ball.ka[3][0])/6,
ball.v[1]+dt*(ball.ka[0][1]+2*ball.ka[1][1]+2*ball.ka[2][1]+ball.ka[3][1])/6]
ball.r = [ball.r[0]+dt*(ball.kv[0][0]+2*ball.kv[1][0]+2*ball.kv[2][0]+ball.kv[3][0])/6,
ball.r[1]+dt*(ball.kv[0][1]+2*ball.kv[1][1]+2*ball.kv[2][1]+ball.kv[3][1])/6]
for ball in balls:
ball.draw(SCREEN, width, height)
for i in range(0, len(ball.spr)):
ball1 = ball
ball2 = balls[ball.spr[i]]
pygame.draw.line(SCREEN, (0, 0, 155), (
ball1.r[0]+width/2, ball1.r[1]+height/2), (ball2.r[0]+width/2, ball2.r[1]+height/2))
# check for energy
KE = 0
EPE = 0
GPE = 0
for i in range(0, len(springs)):
EPE += 1/2 * k * \
(L - dist(balls[springs[i][0]].r,
balls[springs[i][1]].r))**2
for i in range(0, len(balls)):
KE += balls[i].KE()
GPE += balls[i].GPE()
if initE == 0:
initE += KE+EPE+GPE
text1 = font.render(f"initial energy: {str(round(initE, digits))}", True, (255, 255, 255))
text2 = font.render(f"kinetic energy: {str(round(KE, digits))}", True, (255, 255, 255))
text3 = font.render(f"elastic potential energy: {str(round(EPE, digits))}", True, (255, 255, 255))
text4 = font.render(f"gravitational energy: {str(round(GPE, digits))}", True, (255, 255, 255))
text5 = font.render(f"total energy: {str(round(KE + EPE + GPE, digits))}", True, (255, 255, 255))
text6 = font.render(f"change in energy: {str(round(KE + EPE + GPE - initE, digits))}", True, (255, 255, 255))
SCREEN.blit(text1, (10, 10))
SCREEN.blit(text2, (10, 60))
SCREEN.blit(text3, (10, 110))
SCREEN.blit(text4, (10, 160))
SCREEN.blit(text5, (10, 210))
SCREEN.blit(text6, (10, 260))
pygame.display.flip()

The immediate error seems to be this
for ball in balls:
...
newb1.append(ball)
...
as ball is just a reference to the class ball instance, thus newb1 is a list of references to the objects in balls, it makes no difference if you manipulate the one or the other, it is always the same data records that get changed.
You need to apply a copy mechanism, as you have lists of lists, you need a deep copy, or a dedicated copy member method, else you just copy the array references in the ball instances, so you get different instances, but pointing to the same arrays.
It is probably not an error but still a bad idea to have the class name also as variable name in the same scope.

Related

Generate random Gaussian noise MTLTexture or MTLBuffer of size (width, height)

I am writing a real-time video filter application and for one of the algorithms I want to try out, I need to generate a random, gaussian univariate distributed buffer (or texture) based on the input source.
Coming from a Python background, the following few lines are running in about 0.15s (which is not real-time worthy but a lot faster than the Swift code I tried below):
h = 1170
w = 2532
with Timer():
noise = np.random.normal(size=w * h * 3)
plt.imshow(noise.reshape(w,h,3))
plt.show()
My Swift code try:
private func generateNoiseTextureBuffer(width: Int, height: Int) -> [Float] {
let w = Float(width)
let h = Float(height)
var noiseData = [Float](repeating: 0, count: width * height * 4)
for xi in (0 ..< width) {
for yi in (0 ..< height) {
let index = yi * width + xi
let x = Float(xi)
let y = Float(yi)
let random = GKRandomSource()
let gaussianGenerator = GKGaussianDistribution(randomSource: random, mean: 0.0, deviation: 1.0)
let randX = gaussianGenerator.nextUniform()
let randY = gaussianGenerator.nextUniform()
let scale = sqrt(2.0 * min(w, h) * (2.0 / Float.pi))
let rx = floor(max(min(x + scale * randX, w - 1.0), 0.0))
let ry = floor(max(min(y + scale * randY, h - 1.0), 0.0))
noiseData[index * 4 + 0] = rx + 0.5
noiseData[index * 4 + 1] = ry + 0.5
noiseData[index * 4 + 2] = 1
noiseData[index * 4 + 3] = 1
}
}
return noiseData
}
...
let noiseData = self.generateNoiseTextureBuffer(width: context.sourceColorTexture.width, height: context.sourceColorTexture.height)
let noiseDataSize = noiseData.count * MemoryLayout.size(ofValue: noiseData[0])
self.noiseBuffer = device.makeBuffer(bytes: noiseData, length: noiseDataSize)
How can I accomplish this fast and easily in Swift?

Straighten contours OpenCV

Hello guys I would like to "straighten" some contours using opencv/python. Is there anyway to accomplish this?
I have attached two images:
in the current stage
and how I would like to see it .
Bounding boxes resolve the majority of the problem, but there are some exceptions that do not produce the desired outcome (see the top right contour in the image).
Thank you very much!
Current Contours
Squared Contours
def approximate_contours(image: np.ndarray, eps: float, color=(255, 255, 255)):
contours, _ = cv.findContours(image, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
image = cv.cvtColor(image, cv.COLOR_GRAY2BGR)
approx_contours = []
for cnt in contours:
epsilon = eps * cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, epsilon, True)
cv.drawContours(image, [approx], -1, color=color)
approx_contours.append(approx)
return image, approx_contours
def get_angle(pts: np.ndarray):
a = np.array([pts[0][0][0], pts[0][0][1]])
b = np.array([pts[1][0][0], pts[1][0][1]])
c = np.array([pts[2][0][0], pts[2][0][1]])
ba = a - b
bc = c - b
unit_vector_ba = ba / np.linalg.norm(ba)
unit_vector_bc = bc / np.linalg.norm(bc)
dot_product = np.dot(unit_vector_ba, unit_vector_bc)
angle_rad = np.arccos(dot_product)
angle_deg = degrees(angle_rad)
try:
int(angle_deg)
except Exception as e:
raise Exception("nan value detected")
return int(angle_deg)
def move_points(contour:np.ndarray, pts: np.ndarray, angle: int, ext: list, weight=1):
(ext_left, ext_right, ext_bot, ext_top) = ext
a = np.array([pts[0][0][0], pts[0][0][1]])
b = np.array([pts[1][0][0], pts[1][0][1]])
c = np.array([pts[2][0][0], pts[2][0][1]])
right_angle = False
if 45 < angle < 135:
right_angle = True
diff_x_ba = abs(b[0] - a[0])
diff_y_ba = abs(b[1] - a[1])
diff_x_bc = abs(b[0] - c[0])
diff_y_bc = abs(b[1] - c[1])
rap_ba = diff_x_ba / max(diff_y_ba, 1)
rap_bc = diff_x_bc / max(diff_y_bc, 1)
if rap_ba < rap_bc:
a[0] = int((a[0] * weight + b[0]) / (2 + weight - 1))
b[0] = a[0]
c[1] = int((c[1] + b[1]) / 2)
b[1] = c[1]
else:
c[0] = int((c[0] + b[0]) / 2)
b[0] = c[0]
a[1] = int((a[1] * weight + b[1]) / (2 + weight - 1))
b[1] = a[1]
else:
diff_x_ba = abs(b[0] - a[0])
diff_y_ba = abs(b[1] - a[1])
diff_x_bc = abs(b[0] - c[0])
diff_y_bc = abs(b[1] - c[1])
if (diff_x_ba + diff_x_bc) > (diff_y_ba + diff_y_bc):
a[1] = int((a[1] * weight + b[1] + c[1]) / (3 + weight - 1))
b[1] = a[1]
c[1] = a[1]
else:
a[0] = int((a[0] * weight + b[0] + c[0]) / (3 + weight - 1))
b[0] = a[0]
c[0] = a[0]
return a, b, c, right_angle
def straighten_contours(contours: list, image: np.ndarray, color=(255, 255, 255)):
image = cv.cvtColor(image, cv.COLOR_GRAY2BGR)
for cnt in contours:
idx = 0
ext_left = cnt[cnt[:, :, 0].argmin()][0]
ext_right = cnt[cnt[:, :, 0].argmax()][0]
ext_top = cnt[cnt[:, :, 1].argmin()][0]
ext_bot = cnt[cnt[:, :, 1].argmax()][0]
while idx != int(cnt.size / 2):
try:
angle = get_angle(cnt[idx:idx + 3])
except Exception:
idx += 1
continue
(a, b, c, right_angle) = move_points(cnt, cnt[idx:idx + 3], angle, [ext_left, ext_right, ext_bot, ext_top])
cnt[idx][0] = a
cnt[idx + 1][0] = b
cnt[idx + 2][0] = c
idx += 1
if not right_angle:
idx -= 1
cnt = np.delete(cnt, (idx + 1), 0)
if idx == 1:
cnt = np.append(cnt, cnt[:2], axis=0)
cnt = np.delete(cnt, [0, 1], 0)
cv.drawContours(image, [cnt], -1, color=color)
return image
I managed to do some workarounds. The straighten contours function is applied onto the approximate_contours result (the first image in the question). Is not as good as I would have wanted it to be but it works.

Generate random movement for a point in 3D space

I want to simulate a point that moves with random vibration around a mean position (let's say around the position [X, Y, Z] = [0,0,0]). The first solution that I found is to sum a couple of sinusoids for each axis based on the following equation:
<img src="https://latex.codecogs.com/gif.latex?\sum_{i&space;=&space;1}^n&space;A_i&space;\sin(\omega_i&space;t&plus;\phi)" title="\sum_{i = 1}^n A_i \sin(\omega_i t+\phi)" />
where A_i is a normal random amplitude, and omega_i is a normal random frequency. I have not tested the phase yet, so I leave it to zero for now. I generated figures of the expect normal distribution and equation results with the following approach. I tried multiple values of N and I'm not sure that the equation is giving a normally distributed results. Is my approach correct? Is there a better way to generate random vibration?
For such a task, you may find useful Perlin Noise or even Fractal Brownian Motion noise. See this implementation in JavaScript:
class Utils {
static Lerp(a, b, t) {
return (1 - t) * a + t * b;
}
static Fade(t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
}
class Noise {
constructor() {
this.p = [];
this.permutationTable = [];
this.grad3 = [[1, 1, 0], [-1, 1, 0], [1, -1, 0],
[-1, -1, 0], [1, 0, 1], [-1, 0, 1],
[1, 0, -1], [-1, 0, -1], [0, 1, 1],
[0, -1, 1], [0, 1, -1], [0, -1, -1]];
for (let i = 0; i < 256; i++)
this.p[i] = Math.floor(Math.random() * 256);
for (let i = 0; i < 512; i++)
this.permutationTable[i] = this.p[i & 255];
}
PerlinDot(g, x, y, z) {
return g[0] * x + g[1] * y + g[2] * z;
}
PerlinNoise(x, y, z) {
let a = Math.floor(x);
let b = Math.floor(y);
let c = Math.floor(z);
x = x - a;
y = y - b;
z = z - c;
a &= 255;
b &= 255;
c &= 255;
let gi000 = this.permutationTable[a + this.permutationTable[b + this.permutationTable[c]]] % 12;
let gi001 = this.permutationTable[a + this.permutationTable[b + this.permutationTable[c + 1]]] % 12;
let gi010 = this.permutationTable[a + this.permutationTable[b + 1 + this.permutationTable[c]]] % 12;
let gi011 = this.permutationTable[a + this.permutationTable[b + 1 + this.permutationTable[c + 1]]] % 12;
let gi100 = this.permutationTable[a + 1 + this.permutationTable[b + this.permutationTable[c]]] % 12;
let gi101 = this.permutationTable[a + 1 + this.permutationTable[b + this.permutationTable[c + 1]]] % 12;
let gi110 = this.permutationTable[a + 1 + this.permutationTable[b + 1 + this.permutationTable[c]]] % 12;
let gi111 = this.permutationTable[a + 1 + this.permutationTable[b + 1 + this.permutationTable[c + 1]]] % 12;
let n000 = this.PerlinDot(this.grad3[gi000], x, y, z);
let n100 = this.PerlinDot(this.grad3[gi100], x - 1, y, z);
let n010 = this.PerlinDot(this.grad3[gi010], x, y - 1, z);
let n110 = this.PerlinDot(this.grad3[gi110], x - 1, y - 1, z);
let n001 = this.PerlinDot(this.grad3[gi001], x, y, z - 1);
let n101 = this.PerlinDot(this.grad3[gi101], x - 1, y, z - 1);
let n011 = this.PerlinDot(this.grad3[gi011], x, y - 1, z - 1);
let n111 = this.PerlinDot(this.grad3[gi111], x - 1, y - 1, z - 1);
let u = Utils.Fade(x);
let v = Utils.Fade(y);
let w = Utils.Fade(z);
let nx00 = Utils.Lerp(n000, n100, u);
let nx01 = Utils.Lerp(n001, n101, u);
let nx10 = Utils.Lerp(n010, n110, u);
let nx11 = Utils.Lerp(n011, n111, u);
let nxy0 = Utils.Lerp(nx00, nx10, v);
let nxy1 = Utils.Lerp(nx01, nx11, v);
return Utils.Lerp(nxy0, nxy1, w);
}
FractalBrownianMotion(x, y, z, octaves, persistence) {
let total = 0;
let frequency = 1;
let amplitude = 1;
let maxValue = 0;
for(let i = 0; i < octaves; i++) {
total = this.PerlinNoise(x * frequency, y * frequency, z * frequency) * amplitude;
maxValue += amplitude;
amplitude *= persistence;
frequency *= 2;
}
return total / maxValue;
}
}
With Fractal Brownian Motion can have huge control about the randomness of distribution. You can set the scale, initial offset and its increment for each axis, octaves, and persistence. You can generate as many positions you like by incrementing the offsets, like this:
const NUMBER_OF_POSITIONS = 1000;
const X_OFFSET = 0;
const Y_OFFSET = 0;
const Z_OFFSET = 0;
const X_SCALE = 0.01;
const Y_SCALE = 0.01;
const Z_SCALE = 0.01;
const OCTAVES = 8;
const PERSISTENCE = 2;
const T_INCREMENT = 0.1;
const U_INCREMENT = 0.01;
const V_INCREMENT = 1;
let noise = new Noise();
let positions = [];
let i = 0, t = 0, u = 0, v = 0;
while(i <= NUMBER_OF_POSITIONS) {
let position = {x:0, y:0, z:0};
position.x = noise.FractalBrownianMotion((X_OFFSET + t) * X_SCALE, (Y_OFFSET + t) * Y_SCALE, (Z_OFFSET + t) * Z_SCALE, OCTAVES, PERSISTENCE);
position.y = noise.FractalBrownianMotion((X_OFFSET + u) * X_SCALE, (Y_OFFSET + u) * Y_SCALE, (Z_OFFSET + u) * Z_SCALE, OCTAVES, PERSISTENCE);
position.z = noise.FractalBrownianMotion((X_OFFSET + v) * X_SCALE, (Y_OFFSET + v) * Y_SCALE, (Z_OFFSET + v) * Z_SCALE, OCTAVES, PERSISTENCE);
positions.push(position);
t += T_INCREMENT;
u += U_INCREMENT;
v += V_INCREMENT;
i++;
}
Positions you get with these options would look similar to these:
...
501: {x: 0.0037344935483775883, y: 0.1477509219864437, z: 0.2434570202517206}
502: {x: -0.008955635460317357, y: 0.14436114483299245, z: -0.20921147024725012}
503: {x: -0.06021806450587406, y: 0.14101769272762685, z: 0.17093922757597568}
504: {x: -0.05796055906294283, y: 0.13772732578136435, z: 0.0018755951606465138}
505: {x: 0.02243901814464688, y: 0.13448621540816477, z: 0.013341084536334057}
506: {x: 0.05074194554980439, y: 0.1312810723109357, z: 0.15821600463130164}
507: {x: 0.011075140752144507, y: 0.12809058766450473, z: 0.04006055269090941}
508: {x: -0.0000031848272303249632, y: 0.12488712875549206, z: -0.003957905411646261}
509: {x: -0.0029798194097060307, y: 0.12163862278870072, z: -0.1988934273517602}
510: {x: -0.008762098499026483, y: 0.11831055728747841, z: 0.02222898347134993}
511: {x: 0.01980289423585394, y: 0.11486802263767962, z: -0.0792283303765883}
512: {x: 0.0776034130079849, y: 0.11127772191732693, z: -0.14141576745502138}
513: {x: 0.08695806478169149, y: 0.10750987521108693, z: 0.049654228704645}
514: {x: 0.036915612100698, y: 0.10353995005320946, z: 0.00033977899920740567}
515: {x: 0.0025923223158845687, y: 0.09935015632822117, z: -0.00952549797548823}
516: {x: 0.0015456084571764527, y: 0.09493065267319889, z: 0.12609905321632175}
517: {x: 0.0582996941155056, y: 0.09028042189611517, z: -0.27532974820612816}
518: {x: 0.19186052966982514, y: 0.08540778482478142, z: -0.00035058098387404606}
519: {x: 0.27063961068049447, y: 0.08033053495775729, z: -0.07737309686568927}
520: {x: 0.20318957178662056, y: 0.07507568989311474, z: -0.14633819135757353}
...
Note: for efficiency, it's a good idea generate all positions only once into an array of positions like in this example, and then in some animation loop just assigning positions to your point from this array one by one.
Bonus: Here you can see how those values affect the distribution of multiple points by playing around with real-time response control panel:
https://marianpekar.github.io/fbm-space/
References:
https://en.wikipedia.org/wiki/Fractional_Brownian_motion
https://en.wikipedia.org/wiki/Perlin_noise

Local Binary Patterns features

I have a dataset of images that differ in sizes, and when I extract the local binary pattern (uniform local binary 59 feature) from them I get several features as zeros, is it acceptable as features, if not, and how to deal with it
You can use this solution: https://github.com/arsho/local_binary_patterns
def get_pixel(img, center, x, y):
new_value = 0
try:
if img[x][y] >= center:
new_value = 1
except:
pass
return new_value
def lbp_calculated_pixel(img, x, y):
center = img[x][y]
val_ar = []
val_ar.append(get_pixel(img, center, x-1, y+1)) # top_right
val_ar.append(get_pixel(img, center, x, y+1)) # right
val_ar.append(get_pixel(img, center, x+1, y+1)) # bottom_right
val_ar.append(get_pixel(img, center, x+1, y)) # bottom
val_ar.append(get_pixel(img, center, x+1, y-1)) # bottom_left
val_ar.append(get_pixel(img, center, x, y-1)) # left
val_ar.append(get_pixel(img, center, x-1, y-1)) # top_left
val_ar.append(get_pixel(img, center, x-1, y)) # top
power_val = [1, 2, 4, 8, 16, 32, 64, 128]
val = 0
for i in range(len(val_ar)):
val += val_ar[i] * power_val[i]
return val
# Function to generate horizontal projection profile
def getHorizontalProjectionProfile(image):
# Convert black spots to ones
image[image == 0] = 1
# Convert white spots to zeros
image[image == 255] = 0
horizontal_projection = np.sum(image, axis = 1)
return horizontal_projection
# Function to generate vertical projection profile
def getVerticalProjectionProfile(image):
# Convert black spots to ones
image[image == 0] = 1
# Convert white spots to zeros
image[image == 255] = 0
vertical_projection = np.sum(image, axis = 0)
return vertical_projection
lenx = x_train.shape[0]
x_train_lbp = np.zeros((lenx, 32,32))
for NUM in range(lenx):
gray = rgb2gray(x_train[NUM])
for i in range(0, 32):
for j in range(0, 32):
x_train_lbp[NUM][i][j] = lbp_calculated_pixel(gray, i, j)
lenx = x_test.shape[0]
x_test_lbp = np.zeros((lenx, 32,32))
for NUM in range(lenx):
gray = rgb2gray(x_test[NUM])
for i in range(0, 32):
for j in range(0, 32):
x_test_lbp[NUM][i][j] = lbp_calculated_pixel(gray, i, j)
from sklearn import preprocessing
lenx = x_train.shape[0]
x_train_lbp_vector = np.zeros((lenx, 64))
for NUM in range(lenx):
horizontal_projection = getHorizontalProjectionProfile(x_train_lbp[NUM])
vertical_projection = getVerticalProjectionProfile(x_train_lbp[NUM])
x_train_lbp_vector[NUM] = np.concatenate((horizontal_projection, vertical_projection), axis=0)
x_train_lbp_vector[NUM] = normalize(x_train_lbp_vector[NUM].reshape(1, -1))
lenx = x_test.shape[0]
x_test_lbp_vector = np.zeros((lenx, 64))
for NUM in range(lenx):
horizontal_projection = getHorizontalProjectionProfile(x_test_lbp[NUM])
vertical_projection = getVerticalProjectionProfile(x_test_lbp[NUM])
x_test_lbp_vector[NUM] = np.concatenate((horizontal_projection, vertical_projection), axis=0)
x_test_lbp_vector[NUM] = normalize(x_test_lbp_vector[NUM].reshape(1, -1))

Where a vector would intersect the screen if extended towards it's direction (swift)

I'm trying to write a function in swift, which returns a CGPoint where the extension of a vector (which is within a screen) will intersect the screen. Let's assume that the screen is 800 x 600. It's like the scheme:
The function should have the following parameters:
func calcPoint(start: CGPoint, end: CGPoint) -> CGPoint
start: CGPoint(x: x1, y: y1) - this is the beginning of the vector.
end: CGPoint(x: x1, y: y1) - this is the end point of the vector.
the return point is the one at which the vector intersects the screen (CGPoint(x: x3, y: y3) as shown at the scheme).
The values for the vector start and end are aways points within the screen (the rectangle 0, 0, 800, 600).
EDIT (for Alexander):
Is there a formula, which in the given situation will make it easy to write the function, in not the obvious way using if ... else ... and triangle vertices ratio?
To compute point E you can look at the triangles given by your setting. You have the Triangle ABC and DBE. Note that they are similar, such that we can set up following relation AB : AC = DB : DE using the intercept theorem (AB etc. stands for the line segment between A and B). In the given setting you know all points but E.
Using start and end Points from given setting:
In case start and end have the same x or y-coordinate it is only the top bottom or left right border with the same coordinate.
Using the absolute values it should work for all four corners of your rectangle. Then of course you have to consider E being out of your rectangle, again the same relation can be used AB : AC = D'B : D'E'
A pure swift solution for everyone interested in such (thanks to Ivo Ivanoff):
// Example for iOS
/// The height of the screen
let screenHeight = UIScreen.main.bounds.height
/// The width of the screen
let screenWidth = UIScreen.main.bounds.width
func calculateExitPoint(from anchor : CGPoint, to point: CGPoint) -> CGPoint {
var exitPoint : CGPoint = CGPoint()
let directionV: CGFloat = anchor.y < point.y ? 1 : -1
let directionH: CGFloat = anchor.x < point.x ? 1 : -1
let a = directionV > 0 ? screenHeight - anchor.y : anchor.y
let a1 = directionV > 0 ? point.y - anchor.y : anchor.y - point.y
let b1 = directionH > 0 ? point.x - anchor.x : anchor.x - point.x
let b = a / (a1 / b1)
let tgAlpha = b / a
let b2 = directionH > 0 ? screenWidth - point.x : point.x
let a2 = b2 / tgAlpha
exitPoint.x = anchor.x + b * directionH
exitPoint.y = point.y + a2 * directionV
if (exitPoint.x > screenWidth) {
exitPoint.x = screenWidth
} else if (exitPoint.x < 0) {
exitPoint.x = 0;
} else {
exitPoint.y = directionV > 0 ? screenHeight : 0
}
return exitPoint
}
Any kind of optimizations are welcomed ;-)
There is no single formula, because intersection depends on starting point position, line slope and rectangle size, and it may occur at any rectangle edge.
Here is approach based on parametric representation of line. Works for any slope (including horizontal and vertical). Finds what border is intersected first, calculates intersection point.
dx = end.x - start.x
dy = end.y - start.y
//parametric equations for reference:
//x = start.x + dx * t
//y = start.y + dy * t
//prerequisites: potential border positions
if dx > 0 then
bx = width
else
bx = 0
if dy > 0 then
by = height
else
by = 0
//first check for horizontal/vertical lines
if dx = 0 then
return ix = start.x, iy = by
if dy = 0 then
return iy = start.y, ix = bx
//in general case find parameters of intersection with horizontal and vertical edge
tx = (bx - start.x) / dx
ty = (by - start.y) / dy
//and get intersection for smaller parameter value
if tx <= ty then
ix = bx
iy = start.y + tx * dy
else
iy = by
ix = start.x + ty * dx
return ix, iy