# Find branching points in traces of glowing particles

Hey there, I have a lot of photos in which I am using an angle grinder on a piece of metal. The room was dark and I shot the photos with a couple of seconds of exposure. Therefore I have a lot "light traces" flying through the room with a very high contrast to the background.

These particles tend to split after a certain distance and I would like to make opencv to recognize these splitting points and give me the pixel position.

I have very little to no experience in using opencv so rather detailed explanations would be welcome.

I added a picture which hopefully be helpfull.There you can see one of those branching points, I would like to detect with opencv.

edit retag close merge delete

1

seing, what you tried so far (as in: code) and maybe an example image would be helpful

( 2017-12-01 09:34:20 -0500 )edit

I'm just echoing berak... please put up some code, or at least some images. Marking the bifurcation (or trifurcation or whatnot) points sounds like a very fun project to work on. I would give it my best attempt, if only you could post some images.

( 2017-12-02 12:42:25 -0500 )edit

Actually, I don’t really need pictures... I’ll just use the bifurcation diagram of the logistic function as sample input.

( 2017-12-02 13:27:18 -0500 )edit
1

So I added a picture. Hope, this will help.

( 2017-12-02 15:11:08 -0500 )edit

Holy cow that’s a lot of sparks. :)

( 2017-12-02 15:34:17 -0500 )edit

So, it's taken that the motion of the sparks is to the left, correct? On second thought, I flipped the image horizontally, so the sparks move right.

Right now my main attack will be to cut the image into 1xheight strips, and do an edge detection along the 1D strip. The edge count determines the number of sparks per line. A change in sparks per line means sparks dying or sparks tri/etc/bifurcating.

I wouldn't get your hopes up too much. I'll try my best.

( 2017-12-02 19:29:37 -0500 )edit

I put the code, incomplete as it is, up at: https://github.com/sjhalayka/opencv_s...

( 2017-12-02 20:14:39 -0500 )edit

Thank you so much. I will have look and try to comprehend, what you did.

I guess the problem here is, that lines of seperate sparks can cross, without being split, isnt it?

( 2017-12-03 03:17:57 -0500 )edit

I'm currently stuck in design mode again, which I do before I switch to code mode. What do you think would be the best way to determine if there is branching, and if so, where that branching occurs? Any thoughts?

I also didn't use an edge detection algorithm. I just used the threshold function to convert the grayscale image into a binary image, then did the spark count per vertical line based on that input. If you look at the left-most vertical column in your sample image, you will see that there are 16 "sparks" (white regions). Count them if you like. The right-most vertical column in your sample image has only 4 "sparks" (white regions), so it's much easier (for a human) to count.

Perhaps the solution also relies on counting the black regions? I'm not sure yet.

( 2017-12-03 15:13:41 -0500 )edit

And whatever the solution is, it will be able to handle bifurcations, trifurcations, and whatnot. The solution will also likely rely on the the distinction between white and black.

( 2017-12-03 16:47:32 -0500 )edit

Sort by » oldest newest most voted

My output looks like this, where the blue circles show branching:

As you can see, it works quite well now.

My C++ code (now followed by a Python code) is:

#include <opencv2/opencv.hpp>
using namespace cv;
#pragma comment(lib, "opencv_world331.lib")

#include <iostream>
#include <vector>
using namespace std;

int main(void)
{

if (frame.empty())
{
return -1;
}

Mat colour_frame = frame.clone();

cvtColor(frame, frame, CV_BGR2GRAY);

threshold(frame, frame, 127, 255, THRESH_BINARY);

vector<Point2i> branch_locations;

for (int i = 1; i < frame.cols; i++)
{
bool lit = false;
vector<int> begin_black_regions;
vector<int> end_black_regions;

if (255 == frame.at<unsigned char>(0, i))
{
lit = true;
}
else
{
lit = false;
begin_black_regions.push_back(0);
}

for (int j = 1; j < frame.rows - 1; j++)
{
if (255 == frame.at<unsigned char>(j, i) && lit == false)
{
lit = true;
end_black_regions.push_back(j - 1);
}
else if (0 == frame.at<unsigned char>(j, i) && lit == true)
{
lit = false;
begin_black_regions.push_back(j);
}
}

// End with the last row
if (0 == frame.at<unsigned char>(frame.rows - 1, i) && lit == false)
{
end_black_regions.push_back(frame.rows - 1);
}
else if (0 == frame.at<unsigned char>(frame.rows - 1, i) && lit == true)
{
begin_black_regions.push_back(frame.rows - 1);
end_black_regions.push_back(frame.rows - 1);
}
else if (255 == frame.at<unsigned char>(frame.rows - 1, i) && lit == false)
{
end_black_regions.push_back(frame.rows - 2);
}

if(begin_black_regions.size() != end_black_regions.size())
cout << begin_black_regions.size() << " " << end_black_regions.size() << endl;

// for each black region begin/end pair
for (size_t k = 0; k < begin_black_regions.size(); k++)
{
bool found_branch = true;

for (int l = begin_black_regions[k]; l <= end_black_regions[k]; l++)
{
if (0 == frame.at<unsigned char>(l, i - 1))
{
found_branch = false;
break;
}
}

if (found_branch == true)
{
Point2i location(i - 1, begin_black_regions[k]);
branch_locations.push_back(location);
}
}
}

for (size_t i = 0; i < branch_locations.size(); i++)
circle(colour_frame, branch_locations[i], 2, Scalar(255, 127, 0), 2);

imshow("frame", colour_frame);

waitKey();

return 0;
}


The Python code is:

import cv2
import numpy

if frame is None:
exit()

colour_frame = frame

frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

ret, frame = cv2.threshold(frame, 127, 255, cv2.THRESH_BINARY)

rows = frame.shape[0]
cols = frame.shape[1]

branch_locations = []

for i in range(1, cols):
lit = False
begin_black_regions = []
end_black_regions = []

if 255 == frame[0, i]:
lit = True
else:
lit = False
begin_black_regions.append(0)

for j in range(1, rows - 1):
if 255 == frame[j, i] and lit == False:
lit = True
end_black_regions.append(j - 1)
elif 0 == frame[j, i] and lit == True:
lit = False
begin_black_regions.append(j)

# end with last row
if 0 == frame[rows - 1, i] and lit == False:
end_black_regions.append(rows - 1)
elif 0 == frame[rows - 1, i] and lit == True:
begin_black_regions.append(rows - 1)
end_black_regions.append(rows - 1 ...
more

1

Wow, thanks a lot! This is more than I ever hoped for! I will spend some time trying to tweak the pictures i generate. There are lot of detections due to crossing of two particles. I think the easiest solution is to pick an image section that contains less traces.

( 2017-12-04 16:36:57 -0500 )edit

No problem. Happy holidays! It was a fun project. Good luck with your work going forward. Thank you for marking the answer as correct!

( 2017-12-04 16:44:19 -0500 )edit
1

I wanted to let you know that sometimes there was an odd number of elements in begin_end_black_regions. This was a bug. I redid the part that gets the black begin/end regions, and have updated the code now that the bug is fixed.

What level of a C++ programmer are you? Beginner, Intermediate, Advanced, Expert?

( 2017-12-05 19:09:59 -0500 )edit

I am still playing around with the code and feed different pictures to it, to check its performance. and I am trying to find additional crossing points in the pictures I have already evaluated manually. I am an absolute beginner at c++ (i know a little c, though), but I have started to convert your code into python, which I can handle better.

( 2017-12-06 07:41:13 -0500 )edit

Sounds good. Maybe post your Python code here once you're done with it.

( 2017-12-06 07:50:00 -0500 )edit

I will, but be patient. :D

( 2017-12-06 07:57:25 -0500 )edit

Can I ask what you're planning on doing with the branch finder, in the end?

The C++ Standard Template Library comes with the vector container class, which I use in my code. The vector acts like a C-style array... contiguous memory layout, [ ] operator is overloaded, pointer to vector contents via &my_vec[0], etc. The best thing about vector is that it's easily resizable and automatically deallocates the memory used by the container when the vector goes out of scope. Pointers and new/delete and malloc/free for allocating memory are largely unnecessary in C++... no more memory leaks.

( 2017-12-06 10:12:50 -0500 )edit

Well, my main task for the project (it's a physics tournament) is to determine the mean length of the particles before they split (if they do at all). I already counted about 200 of them manually which was tedious. So I asked myself if it can be counted automatically. But I already did the distribution and it has given me something that I consider OK, so your Code would be a tweak for my overall method of obtaining data if I should ever have to do something like this again.

But we try a lot of stuff with opencv in my machine learning lecture at the university and I my interest is kindled even more now, now that I see that you can do a lot of cool stuff with it

( 2017-12-06 12:05:18 -0500 )edit

Very cool. I was going to say that it could also work on bubble chamber images. :)

( 2017-12-06 12:09:39 -0500 )edit

Yeah, I already thought of that. Another team-member is building a home-made cosmic ray detector. But the contrast is so low, that it might be hard.

But I will try some time, maybe :)

( 2017-12-06 12:12:02 -0500 )edit

Official site

GitHub

Wiki

Documentation