Ask Your Question
1

Find branching points in traces of glowing particles

asked 2017-12-01 08:19:42 -0500

pickniclas gravatar image

updated 2017-12-02 15:10:23 -0500

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. image description

edit retag flag offensive close merge delete

Comments

1

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

berak gravatar imageberak ( 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.

sjhalayka gravatar imagesjhalayka ( 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.

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

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

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

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

sjhalayka gravatar imagesjhalayka ( 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.

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

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

When the code is complete, I will answer your question.

sjhalayka gravatar imagesjhalayka ( 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?

pickniclas gravatar imagepickniclas ( 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.

sjhalayka gravatar imagesjhalayka ( 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.

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

1 answer

Sort by » oldest newest most voted
3

answered 2017-12-04 13:34:14 -0500

sjhalayka gravatar image

updated 2017-12-21 19:04:12 -0500

Please mark this answer as correct if it satisfies your question. :)

I load a flipped version of your input image:

image description

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

image description

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)
{
    Mat frame = imread("sparks.png");

    if (frame.empty())
    {
        cout << "Error loading image file" << endl;
        return -1;
    }

    Mat colour_frame = frame.clone();

    cvtColor(frame, frame, CV_BGR2GRAY);

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

    vector<Point2i> branch_locations;

    // Start with the second column
    for (int i = 1; i < frame.cols; i++)
    {
        bool lit = false;
        vector<int> begin_black_regions;
        vector<int> end_black_regions;

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

        // Start with the second row
        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

frame = cv2.imread('sparks.png')

if frame is None:
    print('Error loading image')
    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 = []

# start with second column
for i in range(1, cols):
    lit = False
    begin_black_regions = []
    end_black_regions = []

    # start with first row
    if 255 == frame[0, i]:
        lit = True
    else:
        lit = False
        begin_black_regions.append(0)

    # start with second row
    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)
edit flag offensive delete link more

Comments

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.

pickniclas gravatar imagepickniclas ( 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!

sjhalayka gravatar imagesjhalayka ( 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?

sjhalayka gravatar imagesjhalayka ( 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.

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

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

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

I will, but be patient. :D

pickniclas gravatar imagepickniclas ( 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.

sjhalayka gravatar imagesjhalayka ( 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

pickniclas gravatar imagepickniclas ( 2017-12-06 12:05:18 -0500 )edit

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

sjhalayka gravatar imagesjhalayka ( 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 :)

pickniclas gravatar imagepickniclas ( 2017-12-06 12:12:02 -0500 )edit
Login/Signup to Answer

Question Tools

1 follower

Stats

Asked: 2017-12-01 08:19:42 -0500

Seen: 164 times

Last updated: Dec 21 '17