Creating Desmos like plots

Ujjayanta Bhaumik
3 min readDec 16, 2021

--

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:

  1. canny edge detection of an input image
  2. detecting contours in the image and selecting which ones to keep based on a criterion (rejected the contours which had too few points(< 3) )
  3. for each contour, opencv returns a set of coordinates defining the contour.
  4. 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)]
  5. 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.

Rayquaza
Abra
A fading Pikachu

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.

--

--

Ujjayanta Bhaumik
Ujjayanta Bhaumik

Written by Ujjayanta Bhaumik

MSc Computer Vision, Graphics and Imaging, University College London

No responses yet