Created
May 5, 2024 22:01
-
-
Save evanthebouncy/9f3df9b5d3194b71ece9ce9126e9be2d to your computer and use it in GitHub Desktop.
rendering a 3 point arc in openCV2 . . . I do not wish what I did upon anyone
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
import matplotlib.pyplot as plt | |
import json | |
import cv2 | |
import math | |
# open the ./data_train.json | |
GRID = 64 | |
IMG_W = GRID * 20 | |
# center and radius of circle, bug free | |
def find_circle(x1, y1, x2, y2, x3, y3): | |
# If the first point coincides with the third point, it is a full circle | |
if x1 == x3 and y1 == y3: | |
# Return the midpoint of the first and second point | |
ret = [(x1 + x2) / 2, (y1 + y2) / 2] | |
print(ret) | |
return ret | |
x12 = (x1 - x2) | |
x13 = (x1 - x3) | |
y12 = (y1 - y2) | |
y13 = (y1 - y3) | |
y31 = (y3 - y1) | |
y21 = (y2 - y1) | |
x31 = (x3 - x1) | |
x21 = (x2 - x1) | |
# x1^2 - x3^2 | |
sx13 = x1**2 - x3**2 | |
# y1^2 - y3^2 | |
sy13 = y1**2 - y3**2 | |
sx21 = x2**2 - x1**2 | |
sy21 = y2**2 - y1**2 | |
f = (sx13 * x12 + sy13 * x12 + sx21 * x13 + sy21 * x13) / (2 * (y31 * x12 - y21 * x13)) | |
g = (sx13 * y12 + sy13 * y12 + sx21 * y13 + sy21 * y13) / (2 * (x31 * y12 - x21 * y13)) | |
c = -(x1**2) - y1**2 - 2 * g * x1 - 2 * f * y1 | |
# Equation of circle: x^2 + y^2 + 2*g*x + 2*f*y + c = 0 | |
# where the center is (h = -g, k = -f) and radius r | |
# as r^2 = h^2 + k^2 - c | |
h = -g | |
k = -f | |
sqr_of_r = h**2 + k**2 - c | |
# r is the radius | |
r = math.sqrt(sqr_of_r) | |
return (h, k), r | |
def render_3pt_arc(entity, img, color, line_width=3): | |
# get and transform the 3 points | |
pt_start, pt_mid, pt_end = entity | |
pt_start = transform(pt_start[0], pt_start[1]) | |
pt_mid = transform(pt_mid[0], pt_mid[1]) | |
pt_end = transform(pt_end[0], pt_end[1]) | |
# check if they are colinear, if they are, raise error | |
term1 = (pt_end[1] - pt_start[1]) * (pt_mid[0] - pt_start[0]) | |
term2 = (pt_mid[1] - pt_start[1]) * (pt_end[0] - pt_start[0]) | |
if (term1 - term2)**2 < 1e-3: | |
raise Colinear | |
center, _ = find_circle(pt_start[0], pt_start[1], pt_mid[0], pt_mid[1], pt_end[0], pt_end[1]) | |
# radius is distance from center to start point | |
radius = math.sqrt((pt_start[0] - center[0])**2 + (pt_start[1] - center[1])**2) | |
# get the start angle | |
start_angle = np.arctan2(pt_start[1] - center[1], pt_start[0] - center[0]) | |
# get the end angle | |
end_angle = np.arctan2(pt_end[1] - center[1], pt_end[0] - center[0]) | |
# get the angle that the circle passes through the mid point | |
mid_angle = np.arctan2(pt_mid[1] - center[1], pt_mid[0] - center[0]) | |
# convert the angles to degrees | |
start_angle = np.degrees(start_angle) | |
end_angle = np.degrees(end_angle) | |
mid_angle = np.degrees(mid_angle) | |
# convert all angles to be between 0 and 360 | |
start_angle = start_angle % 360 | |
end_angle = end_angle % 360 | |
mid_angle = mid_angle % 360 | |
# swap start with end if start is greater than end | |
if start_angle > end_angle: | |
start_angle, end_angle = end_angle, start_angle | |
print (f"start angle: {start_angle}, end angle: {end_angle}, mid angle: {mid_angle}") | |
travel_ang1 = end_angle - start_angle | |
travel_ang2 = - (360 - travel_ang1) | |
print (f"travel angle 1: {travel_ang1}, travel angle 2: {travel_ang2}") | |
# check if start < mid < end, if so, use travel_ang1 | |
if start_angle < mid_angle < end_angle: | |
travel_angle = travel_ang1 | |
else: | |
travel_angle = travel_ang2 | |
center = (int(center[0]), int(center[1])) | |
radius = int(radius) | |
# draw the arc | |
# img = cv2.ellipse(img, (int(x), int(y)), (radius, radius), 0, np.degrees(start_angle), np.degrees(end_angle), color, line_width) | |
# start_angle = start_angle | |
# travel_angle = travel_ang2 | |
img = cv2.ellipse(img, center, (radius, radius), 0, start_angle, start_angle + travel_angle, color, line_width) | |
# draw the center as a circle with radius 3 | |
# img = cv2.circle(img, center, 3, color, line_width) | |
# draw the three control points as circles with radius 3 | |
img = cv2.circle(img, pt_start, 3, color, line_width) | |
img = cv2.circle(img, pt_mid, 3, color, line_width) | |
img = cv2.circle(img, pt_end, 3, color, line_width) | |
return img | |
# make an exception called "colinear" | |
class Colinear(Exception): | |
pass | |
def get_color(): | |
color = np.random.rand(3) | |
# check that it is above 0.5 for at least 1 channel | |
above_half = np.any(color > 0.5) | |
# check that the 3 channels are not close to each other (avoid gray) | |
diff = np.max(color) - np.min(color) | |
sufficient_diff = diff > 0.3 | |
if above_half and sufficient_diff: | |
return color | |
else: | |
return get_color() | |
def transform(x, y): | |
# invert y first for drawing only | |
y = -y | |
''' | |
each point is [x,y] and both x and y range from -32 to 32 | |
put it on the GRID 64x64 | |
and draw it on IMG_W 320x320 | |
''' | |
x = x + GRID | |
y = y + GRID | |
x = int(x * IMG_W / (GRID * 2)) | |
y = int(y * IMG_W / (GRID * 2)) | |
return x, y | |
def to_image(entry, draw_path, use_color=True): | |
# create a blank RBG image | |
img = np.zeros((IMG_W, IMG_W, 3)) | |
# img = np.zeros((IMG_W, IMG_W)) | |
entities = entry['entities'] | |
for entity in entities: | |
# use a random color | |
if use_color: | |
# color is from 0 to 1 | |
color = get_color() | |
else: | |
color = (1, 1, 1) | |
line_width = 4 | |
# it is a line if it has 2 points | |
if len(entity) == 2: | |
(x1, y1), (x2, y2) = entity | |
x1, y1 = transform(x1, y1) | |
x2, y2 = transform(x2, y2) | |
# draw colored line with width 3 | |
img = cv2.line(img, (x1, y1), (x2, y2), color, line_width) | |
# if 4 points it is a circle | |
elif len(entity) == 4: | |
# use only the 1st and 3rd point, that forms the diameter of the circle | |
pt1, pt2 = entity[0], entity[2] | |
pt1 = transform(pt1[0], pt1[1]) | |
pt2 = transform(pt2[0], pt2[1]) | |
# the center is between pt1 and pt2 | |
center = ((pt1[0] + pt2[0])//2, (pt1[1] + pt2[1])//2) | |
# the radius is the distance between pt1 and pt2 divided by 2 | |
radius = int(np.sqrt((pt1[0] - pt2[0])**2 + (pt1[1] - pt2[1])**2) / 2) | |
img = cv2.circle(img, center, radius, color, line_width) | |
# if 3 points it is a 3 pt arc | |
elif len(entity) == 3: | |
try: | |
img = render_3pt_arc(entity, img, color, line_width) | |
except Colinear: | |
raise Colinear | |
else: | |
print ("oops") | |
print (entity) | |
assert 0, "something wrong" | |
# save the image as a png to ./drawings directory | |
if draw_path: | |
plt.imsave(draw_path, img) | |
# show the image | |
plt.imshow(img) | |
plt.show() | |
return img | |
if __name__ == '__main__': | |
from copy import deepcopy | |
# make a 3 point arc 2,0; sqrt2/2, sqrt2/2; 0,2 | |
try_arc1 = [[0, 1], [np.sqrt(2)/2, np.sqrt(2)/2], [1, 0]] | |
try_arc2 = deepcopy(try_arc1) | |
# negate the mid point | |
try_arc2[1] = [-x for x in try_arc2[1]] | |
try_arc3 = [[-2, -2], [2, -2], [-2, 0]] | |
entry = {'entities': [[[-2, -2], [2, -2], [-2, 4]], | |
[[-2, -2], [0, 0], [-2, 5]], | |
[[-2, -2], [-1, -1], [-2, 6]], | |
[[-2, -2], [-3, 0], [-2, 7]], | |
[[-2, -2], [-4, 0], [-2, 8]], | |
[[-2, -2], [-5, 0], [-2, 9]]]} | |
# scale by 10 for better visualization | |
for entity in entry['entities']: | |
for i in range(len(entity)): | |
entity[i] = [3 * x for x in entity[i]] | |
try: | |
img = to_image(entry, './drawings/try_arc.png') | |
except Colinear: | |
print ("colinear arc, skipping...") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment