I have such images

I want to draw a red line in every line content like this:

If I accumulate the pixel value for every line, I can get the red curve line:

black = ImageRotate[ColorNegate[Import["https://i.stack.imgur.com/skpSZ.png"]]];
data = Plus @@ ImageData[black];
Show[black, ListLinePlot[data, PlotStyle -> Red]]

I think the position of the yellow arrow is what I want to find... But how to find it? Of course, I know the number of the line must be 10 in every column. Because the digits are always from 0 to 9. And I have another such images to do such thing..





2 Answers 2


Not as succinct as I'd hoped, but here it is:

highlight[img_] := Module[
  {binary, pixelsPerRow, clusters, diffs, occupied, empty, intervals, 
   means, imgw, imgh},
  padded = ImagePad[img, 1];
  binary = Binarize[ColorNegate[padded]];
  pixelsPerRow = Total[ImageData[binary], {2}];
  clusters = FindClusters[
    pixelsPerRow -> Range@Length[pixelsPerRow], 5,
    Method -> "KMeans"
  diffs = Differences[First[clusters]];
  occupied = DeleteCases[diffs, 1];
  empty = SequenceCases[diffs, {seq : 1 ..} :> Length[{seq}]];
  intervals = Partition[Accumulate@Riffle[empty, occupied], 2];
  means = Pick[Mean /@ intervals, EuclideanDistance[##] > 15 & @@@ intervals];
  {imgw, imgh} = ImageDimensions[img];
  HighlightImage[img, InfiniteLine[{0, imgh - # + 1}, {1, 0}] & /@ means]

The basic idea is that rows where there is no text always have the same, or almost the same, number of white pixels. They also have fewer pixels than any other rows. This means that if we cluster the pixel counts per row then the first cluster will consist of the empty rows. We can then use those row numbers to figure out where to put the red lines.

There is a magic number, 15, in this algorithm. Without any correction, this algorithm would have drawn red lines through horizontal white lines because they aren't empty. What the number 15 denotes is that if non-empty sequences of rows are shorter than fifteen rows then they shouldn't be highlighted with red lines.


img1 = Import["https://i.stack.imgur.com/aJbkm.png"];
img2 = Import["https://i.stack.imgur.com/hetHU.png"];
img3 = Import["https://i.stack.imgur.com/7CU49.png"];

GraphicsColumn[highlight /@ {img1, img2, img3}, ImageSize -> 400]


  • $\begingroup$ How about this? $\endgroup$
    – yode
    Oct 14, 2018 at 6:19
  • $\begingroup$ @yode Doesn't it work, or what? $\endgroup$
    – C. E.
    Oct 14, 2018 at 6:24
  • $\begingroup$ Yes, I will get this result $\endgroup$
    – yode
    Oct 14, 2018 at 6:26
  • $\begingroup$ @yode ok, I see. It was easily fixed by padding the image. I've updated the function. $\endgroup$
    – C. E.
    Oct 14, 2018 at 6:46
  • $\begingroup$ Then img2 will have an extra red line... $\endgroup$
    – yode
    Oct 14, 2018 at 7:10

Just a start to try,

img = Import["https://i.stack.imgur.com/hoCAX.png"]

whiteCount = Total /@ ImageData[Binarize@img];
median = Median[whiteCount];
tmp = SplitBy[whiteCount, Sign[# - median] &];
pos = Pick[Accumulate[Length /@ tmp] - 1/2 Length /@ tmp, 
   Length[#] > 5 && #[[1]] < median & /@ tmp];
dims = ImageDimensions[img];
 Line[{{1, dims[[2]] - #}, {dims[[1]], dims[[2]] - #}} & /@ pos]]

But this code cannot success to use in other images.. Because I have use the median as a threshold here...


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.