please format code with </> button * homework policy * asking questions
Dear friends
I uploaded an image for which I would estimate some dimensions, there are central circles and associated circles around to measure their radii.
I’m a MATLAB programmer and I’m a newbie in Python.
I need your help to write a code that automatically detects the line and circle and measure them with the best accuracy.
I can tell there will be a lot of questions asking you to clarify…
-
Do you mean you want to analyze an b/w image?
-
Is the size of the shape always the same?
-
Do you need the rotation angle?
Hey, and welcome to the forum, great to have you here!
Hi @Ezzat_Oraby and welcome,
As @Chrisir said, your question is quite open to really give you proper advice but I think you can go have a look to the openCV library for processing. It can be a good starting point to analyze your image.
Hi @Chrisir, Hi @jb4x
I’ve uploaded another image with a problem of line detection but I discovered that my uploaded images are limited to one. So, the question will be for circles detection of the BW image and then getting radii
Once again, we need more precision if you want to have any useful advice:
- Will it be scanned images or pictures (or both)?
- If it is scanned, is it always with the same crateria (ppi is an important parameter there)?
- Is the scale always the same across all your images?
- Is the shape to detect always the same?
- Is there some visual element that are always there that could help detect the center for example?
- Are the line thicknesses always about the same?
- I can see on your image that you have plenty of marks, will they always be there and always be identical?
- What about the “bumps” all around the main circle? Is the radius the one of the main circle, the one at the edge of those bumps?
- Is there several to detect per images?
I can go on and on but depending on the task, you can try coding something very specialized and thus using some “shortcuts” or have to deal with more global trickier code. Maybe if you have several kind of shapes to detect it could be interesting to sort them beforehand for example or at least have a piece of code that sort them.
Try giving us the most information you can so people here can lead you to the best solution.
And it’s too bad that you are limited to 1 picture, it would have been nice to see other examples with drawings on top to clarify everything.
Ok, the main task of my work is to evaluate a real coloured image taken by some mobile camera maybe 25 MB Pixel for the objective and the standard length with which I will compare is in the image like the attached file.
Sorry, I asked about the BW while the main for me is the real coloured image
Also, the circular pattern is of the most importance, but not all
I have linear dimensions and rectangular shapes
for example, I’m working on a circular sieve sheet calibration and I would like to take an image for a portion of the sieve may have about 20 small circles to be averaged by measuring each circle.
this method would be compared to the ordinary method that is done by measuring the total width of the sheet and dividing on number of circles after subtracting the separations
Here is an example of things you can do given the example you provided:
Each pictures represent a step in the process and the last one show the detected center with the detected radius.
Here is the (dirty) code I used:
import gab.opencv.*;
import java.util.*;
class Tuple {
int x, y;
Tuple(int x, int y) {
this.x = x;
this.y = y;
}
}
OpenCV opencv;
PImage src, threshold, floodFillResult;
PrintWriter output;
void setup() {
src = loadImage("res/Example.png");
size(900, 600);
opencv = new OpenCV(this, src);
// Apply threshold to convert to BW
opencv.loadImage(src);
opencv.threshold(60);
threshold = opencv.getSnapshot();
// Flod fill the middle to get most of the shape
int x = threshold.width / 2;
int y = threshold.height / 2;
while (threshold.pixels[y * threshold.width + x] == -16777216) { // Start in the middle and move on the right until finding a white pixels
x++;
}
ArrayList<Tuple> floodArea = flood(threshold, x, y);
PImage floodImage = createImage(threshold.width, threshold.height, RGB);
floodImage.loadPixels();
for (int i = 0; i < floodArea.size(); i++) {
int idx = floodArea.get(i).y * floodImage.width + floodArea.get(i).x;
floodImage.pixels[idx] = -1;
}
floodImage.updatePixels();
// Dilate and erode to get rid of holes
opencv.loadImage(floodImage);
opencv.dilate();
opencv.dilate();
opencv.erode();
opencv.erode();
PImage filledGapImage = opencv.getSnapshot();
// Find center by averaging pixels position of flood filled zone
ArrayList<Tuple> floodArea2 = flood(filledGapImage, filledGapImage.width / 2, filledGapImage.height / 2);
int xTot, yTot, centerX, centerY;
xTot = yTot = 0;
for (int i = 0; i < floodArea2.size(); i++) {
xTot += floodArea2.get(i).x;
yTot += floodArea2.get(i).y;
}
centerX = xTot / floodArea2.size();
centerY = yTot / floodArea2.size();
// Find edges
opencv.loadImage(filledGapImage);
opencv.findCannyEdges(20,75);
PImage edges = opencv.getSnapshot();
// get distance of each point of edges to center
ArrayList<Float> distances = new ArrayList<Float>();
edges.loadPixels();
for (x = 0; x < edges.width; x++) {
for (y = 0; y < edges.height; y++) {
int idx = y * edges.width + x;
if (edges.pixels[idx] == -1) {
distances.add(dist(x, y, centerX, centerY));
}
}
}
// Sort distance by ascending order
Collections.sort(distances);
// Get mean of the first half to compute radius
float radius = 0;
int nbOfDist = distances.size() / 2;
for (int i = 0; i < nbOfDist; i++) {
radius += distances.get(i);
}
radius /= nbOfDist;
image(src, 0, 0);
image(threshold, 300, 0);
image(floodImage, 600, 0);
image(filledGapImage, 0, 300);
image(edges, 300, 300);
image(src, 600, 300);
// Draw center of circle
fill(255, 0, 0);
noStroke();
ellipse(centerX + 600, centerY + 300, 5, 5);
// Draw outside edge of circle
noFill();
stroke(255, 0, 0);
strokeWeight(2);
ellipse(centerX + 600, centerY + 300, radius * 2, radius * 2);
}
ArrayList<Tuple> flood(PImage input, int startX, int startY) {
// Create variables
ArrayList<Tuple> result = new ArrayList<Tuple>();
ArrayList<Tuple> pixelToVisit = new ArrayList<Tuple>();
boolean[] visitedPixel = new boolean[input.width * input.height];
int x, y, idx, targetCol;
// Load pixels from threshold and result
input.loadPixels();
// Init variables
for (int i = 0; i < visitedPixel.length; i++) {
visitedPixel[i] = false;
}
x = startX;
y = startY;
idx = y * input.width + x;
targetCol = input.pixels[idx];
visitedPixel[idx] = true;
pixelToVisit.add(new Tuple(x, y));
while (!pixelToVisit.isEmpty()) {
x = pixelToVisit.get(0).x;
y = pixelToVisit.get(0).y;
idx = y * input.width + x;
result.add(new Tuple(x, y));
// unqueued it
pixelToVisit.remove(0);
// Check adjacent point and add them to queue if white and not visite
if (y - 1 > 0) {
idx = (y - 1) * input.width + x;
if (!visitedPixel[idx] && input.pixels[idx] == -1) {
pixelToVisit.add(new Tuple(x, y - 1));
visitedPixel[idx] = true;
}
}
if (y + 1 < input.height) {
idx = (y + 1) * input.width + x;
if (!visitedPixel[idx] && input.pixels[idx] == -1) {
pixelToVisit.add(new Tuple(x, y + 1));
visitedPixel[idx] = true;
}
}
if (x - 1 > 0) {
idx = y * input.width + (x - 1);
if (!visitedPixel[idx] && input.pixels[idx] == -1) {
pixelToVisit.add(new Tuple(x - 1, y));
visitedPixel[idx] = true;
}
}
if (x + 1 < input.width) {
idx = y * input.width + (x + 1);
if (!visitedPixel[idx] && input.pixels[idx] == -1) {
pixelToVisit.add(new Tuple(x + 1, y));
visitedPixel[idx] = true;
}
}
}
return result;
}
Seeing your setup I think the biggest challenges will be first to get consistent results and second to get accurate results.
For example, since you are taking a picture with a mobile camera, you might not always take the picture at the same distance, with the same lighting setup, with the same hand motion, at the same angle and so on. It means that in the input the contrast, the line thickness, the sharpness (blur) might change a little from picture to picture and those can be important parameters for your algorithm.
For instance, when converting your image to black and white, you need to input a threshold value. If the contrast is a bit lower than usual maybe you will lose some line details that you had before on another image. What you will get is an algorithm that works in your test case but won’t for other cases.
Of course you could use more code magic to find the perfect threshold but I think it is easier to tackle the problem at the origin and find a better setup to ensure more consistent results.
So from what you say I think that the first step would be to think about the best setup you can have in order to ensure the most consistent result as possible. Some ideas:
- Having a fix camera
- Having a mark to position what you want to take a picture of
- Having mostly artificial light setup to avoid outside light contamination
- Find another way to get your real world length. 2mm seems really small to get really precise. (If you have a fixed setup, you could draw a line always visible) In general it is best to avoid 3D object to get distances as it may be difficult to get begining or end of the shape.
Then to go even a step further you can maybe manually edit the image to ease the code part. If you have several shapes, maybe you can separate them and run an algorithm for a dedicated shape. Or, you can also pre-position the circles to tell the tool where they are expected and use that info to improve the result.
I hope i gave you some useful info.
Good luck in your project, it seems really interesting
From your example picture, and with a bit more work, you can also extract the small circles:
Here the really, really messy code:
import gab.opencv.*;
import java.util.*;
class Tuple {
float x, y;
Tuple(float x, float y) {
this.x = x;
this.y = y;
}
}
OpenCV opencv;
PImage src, threshold, floodFillResult;
PrintWriter output;
void setup() {
src = loadImage("res/Example.png");
size(1200, 900);
opencv = new OpenCV(this, src);
// Apply threshold to convert to BW
opencv.loadImage(src);
opencv.threshold(60);
threshold = opencv.getSnapshot();
// Flod fill the middle to get most of the shape
int x = threshold.width / 2;
int y = threshold.height / 2;
while (threshold.pixels[y * threshold.width + x] == -16777216) { // Start in the middle and move on the right until finding a white pixels
x++;
}
ArrayList<Tuple> floodArea = flood(threshold, x, y);
PImage floodImage = createImage(threshold.width, threshold.height, RGB);
floodImage.loadPixels();
for (int i = 0; i < floodArea.size(); i++) {
int idx = (int)floodArea.get(i).y * floodImage.width + (int)floodArea.get(i).x;
floodImage.pixels[idx] = -1;
}
floodImage.updatePixels();
// Dilate and erode to get rid of holes
opencv.loadImage(floodImage);
opencv.dilate();
opencv.dilate();
opencv.erode();
opencv.erode();
PImage filledGapImage = opencv.getSnapshot();
// Find center by averaging pixels position of flood filled zone
ArrayList<Tuple> floodArea2 = flood(filledGapImage, filledGapImage.width / 2, filledGapImage.height / 2);
int xTot, yTot;
float centerX, centerY;
xTot = yTot = 0;
for (int i = 0; i < floodArea2.size(); i++) {
xTot += floodArea2.get(i).x;
yTot += floodArea2.get(i).y;
}
centerX = (float)xTot / floodArea2.size();
centerY = (float)yTot / floodArea2.size();
// Find edges
opencv.loadImage(filledGapImage);
opencv.findCannyEdges(20,75);
PImage edges = opencv.getSnapshot();
// get distance of each point of edges to center
ArrayList<Float> distances = new ArrayList<Float>();
edges.loadPixels();
for (x = 0; x < edges.width; x++) {
for (y = 0; y < edges.height; y++) {
int idx = y * edges.width + x;
if (edges.pixels[idx] == -1) {
distances.add(dist(x, y, centerX, centerY));
}
}
}
// Sort distance by ascending order
Collections.sort(distances);
// Get mean of the first half to compute radius
float radius = 0;
int nbOfDist = distances.size() / 2;
for (int i = 0; i < nbOfDist; i++) {
radius += distances.get(i);
}
radius /= nbOfDist;
// Substract main circle form the filledGapImage to isolate to secondary circles
PGraphics pg = createGraphics(src.width, src.height);
pg.beginDraw();
pg.background(255);
pg.fill(0);
pg.noStroke();
pg.ellipse(centerX, centerY, radius * 2, radius * 2);
pg.endDraw();
PImage smallCircles = pg.get();
smallCircles.loadPixels();
filledGapImage.loadPixels();
for (int i = 0; i < smallCircles.pixels.length; i++) {
smallCircles.pixels[i] = min(smallCircles.pixels[i], filledGapImage.pixels[i]);
}
smallCircles.updatePixels();
filledGapImage.updatePixels();
// Dilate and erode to close gaps
opencv.loadImage(smallCircles);
opencv.erode();
opencv.erode();
opencv.dilate();
opencv.dilate();
PImage smallCirclesFilledGaps = opencv.getSnapshot();
// Find center of mass of each circles
PImage tmpImg = smallCirclesFilledGaps.copy();
ArrayList<ArrayList<Tuple>> smallCirclesArea = new ArrayList<ArrayList<Tuple>>();
ArrayList<Tuple> smallCirclesMassCenters = new ArrayList<Tuple>();
for (x = 0; x < tmpImg.width; x++) {
for (y = 0; y < tmpImg.height; y++) {
int idx = y * tmpImg.width + x;
tmpImg.loadPixels();
if (tmpImg.pixels[idx] == -1) {
smallCirclesArea.add(flood(tmpImg, x, y));
ArrayList<Tuple> smallCirclesPixels = smallCirclesArea.get(smallCirclesArea.size() - 1);
xTot = yTot = 0;
for (int i = 0; i < smallCirclesPixels.size(); i++) {
int z = (int)smallCirclesPixels.get(i).x;
int w = (int)smallCirclesPixels.get(i).y;
tmpImg.pixels[w * tmpImg.width + z] = -16777216;
xTot += z;
yTot += w;
}
smallCirclesMassCenters.add(new Tuple((float)xTot / smallCirclesPixels.size(), (float)yTot / smallCirclesPixels.size()));
}
tmpImg.updatePixels();
}
}
// Find real center
ArrayList<Tuple> smallCirclesCenters = new ArrayList<Tuple>();
for (int i = 0; i < smallCirclesMassCenters.size(); i++) {
fill(0, 255, 0);
noStroke();
float fx = smallCirclesMassCenters.get(i).x;
float fy = smallCirclesMassCenters.get(i).y;
smallCirclesCenters.add(segmentCircleIntersection(centerX, centerY, radius, new Tuple(centerX, centerY), new Tuple(fx, fy)));
}
// Find edges of small circles
opencv.loadImage(smallCirclesFilledGaps);
opencv.findCannyEdges(20,75);
PImage smallEdges = opencv.getSnapshot();
// Get rid of bottom part
pg.beginDraw();
pg.background(255);
pg.fill(0);
pg.noStroke();
pg.ellipse(centerX, centerY, radius * 2 + 3, radius * 2 + 3);
pg.endDraw();
PImage betterSmallEdges = pg.get();
betterSmallEdges.loadPixels();
smallEdges.loadPixels();
for (int i = 0; i < betterSmallEdges.pixels.length; i++) {
betterSmallEdges.pixels[i] = min(betterSmallEdges.pixels[i], smallEdges.pixels[i]);
}
betterSmallEdges.updatePixels();
smallEdges.updatePixels();
// Exemple image from next nest
PImage isolatedSmallCircles = new PImage(src.width, src.height, RGB);
isolatedSmallCircles.loadPixels();
for (int j = 0; j < isolatedSmallCircles.pixels.length; j++) {
isolatedSmallCircles.pixels[j] = -16777216; // Turn all black
}
for (int j = 0; j < smallCirclesArea.get(1).size(); j++) {
x = (int)smallCirclesArea.get(1).get(j).x;
y = (int)smallCirclesArea.get(1).get(j).y;
int idx = y * isolatedSmallCircles.width + x;
isolatedSmallCircles.pixels[idx] = -1; // Turn white one small circle
}
betterSmallEdges.loadPixels();
for (int j = 0; j < isolatedSmallCircles.pixels.length; j++) {
isolatedSmallCircles.pixels[j] = min(isolatedSmallCircles.pixels[j], betterSmallEdges.pixels[j]);
}
isolatedSmallCircles.updatePixels();
betterSmallEdges.updatePixels();
// Get all radius of small center
ArrayList<ArrayList<Float>> smallDistances = new ArrayList<ArrayList<Float>>();
for (int i = 0; i < smallCirclesArea.size(); i++) {
smallDistances.add(new ArrayList<Float>());
tmpImg.loadPixels();
for (int j = 0; j < tmpImg.pixels.length; j++) {
tmpImg.pixels[j] = -16777216; // Turn all black
}
for (int j = 0; j < smallCirclesArea.get(i).size(); j++) {
x = (int)smallCirclesArea.get(i).get(j).x;
y = (int)smallCirclesArea.get(i).get(j).y;
int idx = y * tmpImg.width + x;
tmpImg.pixels[idx] = -1; // Turn white one small circle
}
betterSmallEdges.loadPixels();
for (x = 0; x < tmpImg.width; x++) {
for (y = 0; y < tmpImg.height; y++) {
int j = y * tmpImg.width + x;
if(min(tmpImg.pixels[j], betterSmallEdges.pixels[j]) == -1) {
smallDistances.get(smallDistances.size() - 1).add(dist(smallCirclesCenters.get(i).x, smallCirclesCenters.get(i).y, x, y));
}
}
}
tmpImg.updatePixels();
betterSmallEdges.updatePixels();
}
// Average all distances to get radius of each small circle
ArrayList<Float> smallCircleRadius = new ArrayList<Float>();
for (int i = 0; i < smallDistances.size(); i++) {
float tot = 0;
for (int j = 0; j < smallDistances.get(i).size(); j++) {
tot += smallDistances.get(i).get(j);
}
smallCircleRadius.add(tot / smallDistances.get(i).size());
}
// Average all the radius to get final radius
float smallCircleAvRadius = 0;
for (int i = 0; i < smallCircleRadius.size(); i++) {
smallCircleAvRadius += smallCircleRadius.get(i);
}
smallCircleAvRadius /= smallCircleRadius.size();
// Display the steps
image(src, 0, 0);
image(threshold, 300, 0);
image(floodImage, 600, 0);
image(filledGapImage, 900, 0);
image(edges, 0, 300);
image(src, 300, 300);
image(smallCircles, 600, 300);
image(smallCirclesFilledGaps, 900, 300);
image(smallEdges, 0, 600);
image(betterSmallEdges, 300, 600);
image(isolatedSmallCircles, 600, 600);
image(src, 900, 600);
// Draw center of circle
fill(255, 0, 0);
noStroke();
ellipse(centerX + 300, centerY + 300, 5, 5);
// Draw outside edge of circle
noFill();
stroke(255, 0, 0);
strokeWeight(2);
ellipse(centerX + 300, centerY + 300, radius * 2, radius * 2);
// Draw center of mass of small circles
for (int i = 0; i < smallCirclesMassCenters.size(); i++) {
fill(0, 255, 0);
noStroke();
float fx = smallCirclesMassCenters.get(i).x;
float fy = smallCirclesMassCenters.get(i).y;
ellipse(fx + 900, fy + 300, 5, 5);
}
// Draw real center of small circles
for (int i = 0; i < smallCirclesCenters.size(); i++) {
fill(0, 0, 255);
noStroke();
float fx = smallCirclesCenters.get(i).x;
float fy = smallCirclesCenters.get(i).y;
ellipse(fx + 900, fy + 300, 5, 5);
}
// Draw center of circle
fill(255, 0, 0);
noStroke();
ellipse(centerX + 900, centerY + 600, 5, 5);
// Draw outside edge of circle
noFill();
stroke(255, 0, 0);
strokeWeight(2);
ellipse(centerX + 900, centerY + 600, radius * 2, radius * 2);
// Draw real center of small circles
for (int i = 0; i < smallCirclesCenters.size(); i++) {
fill(0, 255, 0);
noStroke();
float fx = smallCirclesCenters.get(i).x;
float fy = smallCirclesCenters.get(i).y;
ellipse(fx + 900, fy + 600, 5, 5);
}
// Draw real center of small circles
for (int i = 0; i < smallCirclesCenters.size(); i++) {
noFill();
stroke(0, 255, 0);
strokeWeight(2);
float fx = smallCirclesCenters.get(i).x;
float fy = smallCirclesCenters.get(i).y;
ellipse(fx + 900, fy + 600, smallCircleAvRadius * 2, smallCircleAvRadius * 2);
}
}
ArrayList<Tuple> flood(PImage input, int startX, int startY) {
// Create variables
ArrayList<Tuple> result = new ArrayList<Tuple>();
ArrayList<Tuple> pixelToVisit = new ArrayList<Tuple>();
boolean[] visitedPixel = new boolean[input.width * input.height];
int x, y, idx, targetCol;
// Load pixels from threshold and result
input.loadPixels();
// Init variables
for (int i = 0; i < visitedPixel.length; i++) {
visitedPixel[i] = false;
}
x = startX;
y = startY;
idx = y * input.width + x;
targetCol = input.pixels[idx];
visitedPixel[idx] = true;
pixelToVisit.add(new Tuple(x, y));
while (!pixelToVisit.isEmpty()) {
x = (int)pixelToVisit.get(0).x;
y = (int)pixelToVisit.get(0).y;
idx = y * input.width + x;
result.add(new Tuple(x, y));
// unqueued it
pixelToVisit.remove(0);
// Check adjacent point and add them to queue if white and not visite
if (y - 1 > 0) {
idx = (y - 1) * input.width + x;
if (!visitedPixel[idx] && input.pixels[idx] == targetCol) {
pixelToVisit.add(new Tuple(x, y - 1));
visitedPixel[idx] = true;
}
}
if (y + 1 < input.height) {
idx = (y + 1) * input.width + x;
if (!visitedPixel[idx] && input.pixels[idx] == targetCol) {
pixelToVisit.add(new Tuple(x, y + 1));
visitedPixel[idx] = true;
}
}
if (x - 1 > 0) {
idx = y * input.width + (x - 1);
if (!visitedPixel[idx] && input.pixels[idx] == targetCol) {
pixelToVisit.add(new Tuple(x - 1, y));
visitedPixel[idx] = true;
}
}
if (x + 1 < input.width) {
idx = y * input.width + (x + 1);
if (!visitedPixel[idx] && input.pixels[idx] == targetCol) {
pixelToVisit.add(new Tuple(x + 1, y));
visitedPixel[idx] = true;
}
}
}
return result;
}
// Find the points of intersection.
Tuple segmentCircleIntersection(float cx, float cy, float radius, Tuple point1, Tuple point2)
{
float dx, dy, A, B, C, det, t;
dx = point2.x - point1.x;
dy = point2.y - point1.y;
A = dx * dx + dy * dy;
B = 2 * (dx * (point1.x - cx) + dy * (point1.y - cy));
C = (point1.x - cx) * (point1.x - cx) + (point1.y - cy) * (point1.y - cy) - radius * radius;
det = B * B - 4 * A * C;
t = (float)((-B + sqrt(det)) / (2 * A));
if (t > 0 && t < 1) {
return new Tuple(point1.x + t * dx, point1.y + t * dy);
} else {
t = (float)((-B - sqrt(det)) / (2 * A));
return new Tuple(point1.x + t * dx, point1.y + t * dy);
}
}
thank you @jb4x,
I followed your instructions, knowing that I am a MATLAB programmer, but also I was an old Borland C programmer and VBasic.net also, I didn’t write c++ codes but I got used to hacking some VC++ Arduino applications in my work.
Now, I’ve constructed an MFC app. with VC++.net 2010 named Image Analyst and I’ve downloaded Opencv processing folder from Github, please I need your guidance to complete the task, I attached a shot from the GUI of application
which type of file I would put your code.
Hi @Ezzat_Oraby,
First I want to point out that I’m just a self taught hobbyist and I have rarely done any real applications so I am definitely not an expert on that matter. All that to say that I might be wrong and you need to read what I will say with caution and I hope that other more qualified people on this forum will correct me if I’m wrong.
So it seems that what you are trying to do is to create a C++ based application.
Processing is a Java based software/library and OpenCV for processing is a re-write of some of the functionnalities of the OpenCV library designed to work with Processing data types easily. But it features just a fraction of what the full OpenCV library can offer.
So you can’t use it in your project as is or you would have to mix some JAVA files with somme C++ files.
Instead, if you want you can use the “real” OpenCV library to work on a C++ or JAVA environment. And in this case you would be able to make it work with your current setup. On the other hand you will lose the processing layer.
That’s pretty much as far as I can help you on this matter and once again, it would be nice to have the confirmation from other more experienced users.
Dear @jb4x,I will be happy with feed back from all member for the best solution of the problem
If I couldn’t use visual studio.net IDE for running your Java code, so how could I run it ?
Dear @jb4x,
How could I run this code?
On Java script on .net IDE?
or how ??
Hello @Ezzat_Oraby,
If you want to run the code snippet I provided, the simple way is to download and install the processing IDE: https://processing.org/download/
Then simply copy/paste the code and hit the play button.
To be fully working, you will need to create a sub folder named res
and the same placeyou saved your code file and and an Example.png
image there.
Note also that the code I provided is custom fit for the picture I used as an example not any picture.
You will also need to download the OpenCV library for processing and for that, you need, in the processing IDE, to click on Sketch -> Import Library... -> Add Library
. Then search for OpenCV
, click on it and click the install
button.