Giter Club home page Giter Club logo

lingdong- / skeleton-tracing Goto Github PK

View Code? Open in Web Editor NEW
487.0 13.0 62.0 8.16 MB

A new algorithm for retrieving topological skeleton as a set of polylines from binary images

Home Page: https://skeleton-tracing.netlify.app

License: MIT License

C 36.18% C++ 10.63% C# 3.46% HTML 3.43% Processing 0.39% Java 3.17% Makefile 1.71% Python 3.53% Shell 0.31% SWIG 0.25% JavaScript 5.55% Go 4.18% Rust 3.36% Swift 3.25% Julia 1.86% WebAssembly 15.14% Haxe 3.59%
skeletonization computer-vision algorithm computational-geometry polylines

skeleton-tracing's Introduction

Skeleton Tracing

A new algorithm for retrieving topological skeleton as a set of polylines from binary images.

Available in all your favorite languages: C, C++, Java, JavaScript, Python, Go, C#/Unity, Swift, Rust, Julia, WebAssembly, Haxe, Processing, OpenFrameworks.

[Online Demo]

About the Chinese characters in the test image

Introduction

Traditionally, skeletonization (thinning) is a morphological operation to reduce a binary image to its topological skeleton, returning a raster image as result. However, sometimes a vector representation (e.g. polylines) is more desirable. Though contour-finding can be used to further trace the results, they usually give enclosing outlines instead of single strokes, and are prone to slight variations in stroke width caused by imperfection in the skeletonization process. In this demo we present a parallelizable divide-and-conquer based algorithm for skeleton tracing, which converts binary images into a set of polylines, i.e. arrays of (x,y) coordinates along the skeleton, in real time.

Algorithm Description

Define a binary image to be a 2D matrix consisting of 0-pixels(background) and 1-pixels(foreground). The algorithm can be summarized as follows:

  1. Given a binary image, first skeletonize it with a traditional raster skeletonization algorithm, e.g. Zhang-Suen 1984 is used in this demo. (Without this step, the algoirthm still works to a certian extent, though the quality is generally reduced.)
  2. If the width and height of the image are both smaller than a small, pre-determined size, go to step 7.
  3. Raster scan the image to find a row or column of pixels with qualities that best match the following:
    • Has the least amount of 1-pixels on itself.
    • The 2 submatrices divided by this row or column do not have 1-pixels on their four corners.
    • When two or more candidates are found, pick the one that is closer to the center of the image.
  4. Split the image by this column or row into 2 submatrices (either left and right, or top and bottom depending on whether row or column is selected in the previous step).
  5. Check if either of the 2 submatrices is empty (i.e. all 0-pixels). For each non-empty submatrix, recursively process it by going to step 2.
  6. Merge the result from the 2 submatrices, and return the combined set of polylines.
    • For each polylines from one submatrix whose either endpoint coincide with the splitting row or column, find another polyline in the other submatrix whose endpoint meets it. If the matrix was split horizontally, then the x-coordinate of the endpoints should differ by exactly 1, and y-coordinate can differ between 0 to about 4 (depending on the steepness of the stroke potrayed), The reverse goes for vertical splitting.
  7. Recursive bottom. Walk around the 4 edges of this small matrix in either clockwise or ant-clockwise order inspecting the border pixels.
    • Initially set a flag to false, and whenever a 1-pixel is encountered whilst the flag is false, set the flag to true, and push the coordinate of the 1-pixel to a stack.
    • Whenever a 0-pixel is encountered whilst the flag is true, pop the last coordinate from the stack, and push the midpoint between it and the current coordinate. Then set the flag to false.
    • After all border pixels are visited, the stack now holds coordinates for all the "outgoing" (or "incoming") pixels from this small image section. By connecting these coordinates with the center coordinate of the image section, an estimated vectorized representation of the skeleton in this area if formed by these line segments. We further improve the estimate using the following heuristics:
    • If there are exactly 2 outgoing pixels. It is likely that the area holds a straight line. We return a single segment connecting these 2 pixels.
    • If there are 3 or more outgoing pixels, it is likely that the area holds an intersection, or "crossroad". We do a convolution on the matrix to find the 3x3 submatrix that contains the most 1-pixels. Set the center of all the segments to the center of the 3x3 submatrix and return.
    • If there are only 1 outgoing pixels, return the segment that connects it and the center of the image section.

Implementations

Click on links below to see each implementation's documentation and code.

  • C99 (parallelized with pthreads, libpng for reading and X11 for display)
  • C++ (thinly wrapped from C version)
  • JavaScript (WebAssembly compiled from C++ using emscripten)
  • Vanilla JS (Pure JavaScript implementation)
  • Pure Python (slow)
  • Python using C API (compiled from C using SWIG, compatible with numpy and opencv)
  • Java (includes a Processing demo)
  • OpenFrameworks addon (friendly wrapper on C++ version)
  • C# (demo script for Unity Engine)
  • Go (parallelized with goroutines)
  • Swift (demo with NSImage and AppKit)
  • Rust (simple rust implementation)
  • Julia (julia implementation with array views)

Benchmarks

The benchmarks below are produced on MacBook Pro Mid 2015 (2.5 GHz Intel Core i7, 16GB 1600 MHz DDR3). The input image is test_images/opencv-thinning-src-img.png (300x149px).

All the times refer to pure or "vanilla" implemenations, not wrappers of C/C++. Wrappers on C/C++ should be comparable to the performance of C plus some overhead. Exception is WebAssembly performance which depends heavily on browser and number of open tabs.

All the times refer to the "tracing" operation only, excluding the raster thinning step (which is not my algorithm (problem), but note that practically, raster thinning often takes longer than the tracing, especially for images with lots of white pixels).

For compiled languages, the highest possible optimization level is selected, unless otherwise specified.

Ordered by fastest to slowest.

Language Seconds/1000 Runs FPS % C speed Note
C 0.759748 1316 100% -O3
Go 1.1165248 895 68%
Rust 1.39878 714 54% -O
Java 1.722 580 44%
Swift 1.795619 556 42% -O
JavaScript 1.948 513 38% Node.js v12.10
C# 4.266101 234 17% Unity 2018.4.8f1
Julia 4.722 (2.791 amortized) 211 16% First frame takes 2 sec, the rest 0.002 sec
Python 1015.04818 1 0% Python 3.7

Developed at Frank-Ratchye STUDIO for Creative Inquiry at Carnegie Mellon University.

skeleton-tracing's People

Contributors

freder avatar lingdong- avatar radames avatar vipermu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

skeleton-tracing's Issues

Large image with thin lines, all black.

Hello LingDong-,

I'm amazed by the speed of this algorithm. Great work! One issue I encountered is that for large images with quite thin white lines, I end up with no vectors at all. I used csize=1 and many iterations, but to no avail.

Could you help me with that?

Thanks, Frank

UPDATE: The algorithm seems to be non-robust against "artifacts". For reference, this is the image: https://gyazo.com/c11c5dabd97d81b70283cec449a78177
If you remove the artifacts on the right, it works. If you don't, all black.

npm package js version?

hi, this project is very cool! thanks for all the work.

Are you planning to publish the js version as an npm package?
I'd be happy to help craft something if you don't have the time.

Cheers

Error while running ./wasm/compile.sh

When I run ./wasm/compile.sh , I met following error.

% sh ../compile.sh
generating glue...
compiling...
building:ERROR: Closure compiler run failed:

building:ERROR: /var/folders/7r/yw3tw0dx683f315b7vs6n34w0000gn/T/emscripten_temp_rhjrnu9n/trace_skeleton.js.pp.js.jso.js.jso.js.jso.js.jso.js:854:17: ERROR - [JSC_UNDEFINED_VARIABLE] variable intArrayFromString is undeclared
  854|   var intArray = intArrayFromString(value);
                        ^^^^^^^^^^^^^^^^^^

1 error(s), 0 warning(s)

emcc: error: closure compiler failed (rc: 1)

Am I the only one getting this error?
Do you know how to fix this error?

Enviroment

  • mac osx 12.6
  • emcc 3.1.22

swig compilation on macOS

just FYI:
I had to add --undefined dynamic_lookup to the linker command in compile.sh to get it to work on the mac.
what that does exactly is beyond me, but it might be worth adding to the readme to save other people some headache.

How to use individual lines

Hello.

Thank you for this program.

I am very new to coding and working in python.

I would like to skeletonize characters -- akin to some of your own examples:

ho1maskb
hskel

I would like to use individual lines -- the purple one, the blue one, etc. -- to test for certain characteristics. For example, does the image have a vertical line? (Yes.) Does the image have a circle? (No.)

I am not sure how to "grab" or call each colored line. How I would do this?

Ultimately, I am trying to use this as part of template matching to locate these characters on pages. Template matching alone -- at least my efforts thus far at it -- gives too many false positives and too many misses of the template.

Issues building on M1(ARM)

Best I managed to get is to modify the compile to:


swig -python trace_skeleton.i
gcc -fPIC -O3 -c trace_skeleton.c trace_skeleton_wrap.c -I/$(python3-config --cflags)
gcc $(python3-config --ldflags) -dynamiclib *.o -o _trace_skeleton.so -I/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/libpython3.9m.dylib -undefined dynamic_lookup

# quick tests
# python3 -i -c "import trace_skeleton; trace_skeleton.trace('\0\0\0\1\1\1\0\0\0',3,3); print(trace_skeleton.len_polyline());"
# python3 -i -c "import trace_skeleton; print(trace_skeleton.from_list([0,0,0,1,1,1,0,0,0],3,3))"
# python3 example.py

Result:

ImportError: dlopen(/Users/christosavovchristov/symbol-api/api/infrastructure/algorithms/_trace_skeleton.so, 0x0002): tried: '/Users/christosavovchristov/symbol-api/api/infrastructure/algorithms/_trace_skeleton.so' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')), '/System/Volumes/Preboot/Cryptexes/OS/Users/christosavovchristov/symbol-api/api/infrastructure/algorithms/_trace_skeleton.so' (no such file), '/Users/christosavovchristov/symbol-api/api/infrastructure/algorithms/_trace_skeleton.so' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64'))

Index out of bound error when image is given as input on python

Hey Ling,thanks for the excellent code.
I tried running your code(python) on the following images,unfortunately for the first one I am getting index out of bound error
The exact is as follows
index 93 is out of bounds for axis 0 with size 93 at line 245 in trace_skeleton
qua
The same happens for this image also
index 63 is out for bounds for axis 0 with size 63 at line 245
capital_N

And for this image the result is not matching up,any ideas what can be going wrong here
P

How to sort polylines for most optimal total path?

Hey
I was wondering if you can suggest a good way to sort the resulting polyline vector from polylines = ofxTraceSkeleton::trace(im); in such a way that a xy plotter could draw the most efficient path through all of the polylines?
Currently the order is all jumbled.

Dec-15-2021 18-38-42
Here is a small test I did in OpenFrameworks.

I am sure you have already done something like this for https://github.com/CreativeInquiry/PEmbroider

Thanks a bunch.

swig compilation on Ubuntu

Hi @LingDong-
I tested your Python version and it's really nice. I'd need a significant speed up though and I cannot run the swig version:

ImportError: /tf/Repos/trace_skeleton/swig/_trace_skeleton.so: invalid ELF header

I'm running this inside a docker container with gcc version:

gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

My Python version is 3.6.9.

I cannot build from source as I don't have a .dylib file in my system (at least I cannot find it). I tried with

/usr/lib/x86_64-linux-gnu/libpython3.6m.so.1

instead, but it doesn't work:

collect2: error: ld returned 1 exit status

I'm unexperienced with gcc (and C in general) and I've spent a few hours on this already. Any suggestions would be greatly appreciated!

crashes, when calling `trace()` a second time

when I duplicate line 10 in the example the program crashes:

python3(10300,0x109f735c0) malloc: *** error for object 0x15c53e000: pointer being freed was not allocated
python3(10300,0x109f735c0) malloc: *** set a breakpoint in malloc_error_break to debug
./compile.sh: line 8: 10300 Abort trap: 6           python3 example.py

js example for p5.js

Similar to your Processing example for Java, it would be helpful if you could include a basic p5.js example within the js folder.

How to fix xcb error ?

Dear author :

I cam across the following "xcb" error. Please let me know how to fix.

[root@i-pqn3eqgj py]# python3 trace_skeleton.py 
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "/usr/local/lib64/python3.6/site-packages/cv2/qt/plugins" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: xcb.

已放弃

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.