Here is yet an alternative solution to your problem by modelling your question as a 'path optimization problem'. Though it is more complicated than the simple binarization-and-then-curvefitting solution, it is more robust in practice.
From the very high level, we should consider this image as a graph, where
each image pixel is a node on this graph
each node is connected to some other nodes, known as neighbours, and this connection definition is often referred as to the topology of this graph.
each node has a weight (feature, cost, energy, or whatever you want to call it ), reflecting the likelihood that this node is in an optimal central-line we are looking for.
As long as we can model this likelihood, then your problem of finding 'the centerlines of the fringes' becomes to the problem to find local optimal paths on the graph, which can be effectively solved by dynamic programming, e.g. Viterbi algorithm.
Here are some pros of adopting this approach:
all your results will be continuous ( unlike the threshold method that might break one center line into pieces )
a lot of freedoms to construct such a graph, you can select different features, and graph topology.
your results are optimal in the sense of path optimizations
your solution will be more robust against noise, because as long as noise is equally distributed among all pixels, those optimal paths remain stable.
Here is a short demonstration of the above idea. Since I donot use any prior knowledge to specify what are possible starting and ending nodes, I simply decode w.r.t every possible starting node.
For the fuzzy endings, it is caused by the fact that we are looking for optimal paths for every possible ending nodes. As a result, though for some nodes located in dark areas, the highlighted path is still its local optimal one.
For the fuzzy path, you could either smooth it after you find it or use some smoothed features instead of raw intensity.
It is possible to restore partial paths by changing starting and ending nodes.
It will not be difficult to prune these undesired local optimal paths. Because we have the likelihoods of all paths after viterbi decoding, and you may use various prior knowledge ( e.g. we see it is true that we only need one optimal path for those sharing the same source. )
For more details, you may refer to the paper.
Wu, Y.; Zha, S.; Cao, H.; Liu, D., & Natarajan, P. (2014, February). A Markov Chain Line Segmentation Method for Text Recognition. In IS&T/SPIE 26th Annual Symposium on Electronic Imaging (DRR), pp. 90210C-90210C.
Here is a short piece of python code using to make the above graph.
import cv2
import numpy as np
from matplotlib import pyplot
# define your image path
image_path = ;
# read in an image
img = cv2.imread( image_path, 0 );
rgb = cv2.imread( image_path, -1 );
# some feature to reflect how likely a node is in an optimal path
img = cv2.equalizeHist( img ); # equalization
img = img - img.mean(); # substract DC
img_pmax = img.max(); # get brightest intensity
img_nmin = img.min(); # get darkest intensity
# express our preknowledge
img[ img > 0 ] *= +1.0 / img_pmax;
img[ img = 1 :
prev_idx = vt_path[ -1 ].astype('int');
vt_path.append( path_buffer[ prev_idx, time ] );
time -= 1;
vt_path.reverse();
vt_path = np.asarray( vt_path ).T;
# plot found optimal paths for every 7 of them
pyplot.imshow( rgb, 'jet' ),
for row in range( 0, h, 7 ) :
pyplot.hold(True), pyplot.plot( vt_path[row,:], c=np.random.rand(3,1), lw = 2 );
pyplot.xlim( ( 0, w ) );
pyplot.ylim( ( h, 0 ) );