Creating Desmos like plots
Desmos is a graphing calculator that people sometimes misuse for creating amazing graphics. I had created some graphs before :) Roger Federer, Pikachu, Deoxys
For creating these graphs, I had used simple tools mostly like ellipses, parabolas, circles, and straight lines. Believe me! It is a lot of fun. But, it takes a lot of time too.
So, I was thinking of automating it for a long time. I came across this inspirational video on YouTube that is a full fledged Desmos graph generator which uses Bezier curves to produce amazing Graphics. The video: How I animate stuff on Desmos Graphing Calculator. You can see the equations on the left and the plots as well
For this project, I am aiming for something simple though. I want to plot an image just using straight lines and produce those equations which can be imported into Desmos. Let’s get straight into the code.
Libraries used
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
import random
import warnings
warnings.filterwarnings("ignore")
Image Processing
The whole project is based on simple image processing operations:
- canny edge detection of an input image
- detecting contours in the image and selecting which ones to keep based on a criterion (rejected the contours which had too few points(< 3) )
- for each contour, opencv returns a set of coordinates defining the contour.
- for each contour, I divided the contour into pairs of points. For example, if a contour is defined by the points [(1,1),(2,2),(3,3),(4,4)], we get 2 sets [(1,1),(2,2)] and [(3,3),(4,4)]
- For each set, say [(1,1),(2,2)], I fitted a line using y = mx + c. These equations are then pasted in an equation array.
#canny edge detection of input image
#detecting contours in the image and selecting which ones to keep #based on a criteria (rejected the contours which had too few #points(< 3) )img = cv.imread(r"C:\\Users\\Jojo\\Downloads\\fed.jpg")
img = cv.flip(img, 0)
imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
edged = cv.Canny(imgray, 40, 200)
ret, thresh = cv.threshold(edged, 50, 255, 0)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)r = cv.drawContours(img, contours, -1, (0,255,0), 3)
#cv.imshow('img',r)
#cv.waitKey(0)
res = []
s = 0
for i in contours:
if len(i)>3:
res.append(i)
s+=len(i)
r = cv.drawContours(img, res, -1, (0,255,0), 3)
Function for breaking a list into chunks:
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[i:i + n]
Generating equations:
strs = []
pts = []
mb = []
for ele in res:
x = []
y = []
for k in ele:
x.append(k[0][0])
y.append(k[0][1])
if len(x)%2 == 1:
x.pop()
if len(y)%2 == 1:
y.pop()
g = list(chunks(x,2))
h = list(chunks(y,2))
pts.append(tuple(x))
pts.append(tuple(y))
pts.append(random.choice(['r','g','b']))
#pts.append
for i in range(len(g)):
try:
m, b = np.polyfit(g[i], h[i], 1)
st = 'y ='+str(m)+'*x+'+str(b)+'{'+str(min(h[i]))+'<y<'+str(max(h[i]))+'}'+'{'+str(min(g[i]))+'<x<'+str(max(g[i]))+'}'
strs.append(st)
mb.append([m, b, min(g[i]), max(g[i])])
except:
continue
Plotting the equations:
plt.figure(figsize=(10, 10))
plt.grid()
for i in range(len(mb)):
x = np.linspace(mb[i][2],mb[i][3],10)
y = mb[i][0]*x+mb[i][1]
plt.plot(x, y, random.choice(['r','g','b']))
plt.grid()
plt.show()
Results Gallery
Here are some results from my experiment.
There is room for improvements definitely as thresholding and edge detection parameters can be played with. Also, one can get better results with higher degree equations. If you want to collaborate, you can always create a pull request on Github.