# Detecting two (almost) vertical lines, and find the gap between them in (pxl)

I have no knowledge in mathematica, python, and other great softwares.

I saw the following picture in a website;

Where a software detects objects (like bus) and tells about its height or width or such things.

Well, I work in a chemical laboratory. Cameras are used to take pictures of the samples to be analyzed.

See a real picture:

Then I need to measure the distance (the black gap). What I always do is to open the image using Paint in my Personal Computer, select the range manually, and take the reading. See the illustration of what I do:

$$549$$ in this particular example, (I do not need the height like the $$185$$) (only width is required).

My question now, can mathematica, or any other software, detects those two red vertical lines (which are not in the original image, I put them for illustration purpose) and measure the distance between them in (px)?

Some Other examples:

1. Small gap:

2. Not a very clear gap. However, the required distance is the one in the middle (not in the left) white/grey-to-white/gray (by averaging) (Errors are accepted up to +/- 5 px).

If you need me to clarify something, please let me know. I am sorry I do not know English very well. That is the reason.

Any help would be really appreciated. Thanks in advance!

Update 2: A simpler/faster approach to get the gap length:

ClearAll[hWidth]

hWidth = Composition[Max, Cases[p : {0 ..} :> Length[p]],
Drop[#, #[[-1, 1]] - 1] &, Drop[#, 1 - #[[1, 1]]] &,
Split, Map[Max], Transpose, ImageData, MorphologicalBinarize, ImageCrop];

hWidth /@ {img1, img2, img3}

 {548, 17, 61}


or

ClearAll[hWidth2]

hWidth2 = Composition[# - 1 &, Max, Differences,
SparseArray[#]["NonzeroPositions"] &,
Map[Max], Transpose, ImageData, MorphologicalBinarize, ImageCrop];

hWidth2 /@ {img1, img2, img3}

 {548, 17, 61}


A three-step procedure:

1. Full vertical dilation of the input image using Dilation

2. Selecting components (all rectangles after dilation) using SelectComponents and

3. Using ComponentMeasurements to get the "BoundingBox" and "CaliperWidth" of the selected components
ClearAll[vDilate, internalRectangles, dataSet]

vDilate = ColorNegate @
Dilation[MorphologicalBinarize@#, ConstantArray[1, {ImageDimensions[#][[1]], 1}]] &;

internalRectangles[w_: 1] := Rectangle @@@ Values[
ComponentMeasurements[
SelectComponents[vDilate @ #,
#CaliperWidth > w && #AdjacentBorders == {Bottom, Top} &],
"BoundingBox"]] &;

dataSet[w_:1] := ComponentMeasurements[
SelectComponents[vDilate @ #,
#CaliperWidth > w && #AdjacentBorders == {Bottom, Top} &],
{"BoundingBox", "CaliperWidth"}, "Dataset"] &;


Examples:

{img1, img2, img3} = Import /@
{"https://i.stack.imgur.com/kRjAA.jpg",
"https://i.stack.imgur.com/d0ALv.jpg",
"https://i.stack.imgur.com/W5Zcd.jpg"}


For the first two images we get a single rectangle using the default value (1) for the caliper width threshold:

dataSet[] @ img1


HighlightImage[#, internalRectangles[]@#] & @ img1


dataSet[] @ img2


HighlightImage[#, internalRectangles[] @ #] & @ img2


For img3, we get many rectangles with the default caliper width threshold:

dataSet[] @ img3


HighlightImage[#, internalRectangles[]@#] & @ img3


Using a larger value for the threshold caliper width gives a single rectangle:

dataSet[25] @ img3


HighlightImage[#, internalRectangles[25]@#] & @ img3


Alternatively, we can select the rectangle with maximal width from internalRectangles[]@img3:

HighlightImage[#,
MaximalBy[#[[2, 1]] - #[[1, 1]] &]@(internalRectangles[]@#)] &@img3


same picture

Update: To get the internal rectangle with maximum width in a single step, we can define dataSet as follows:

ClearAll[dataSet2]
dataSet2 = MaximalBy[#CaliperWidth &] @
ComponentMeasurements[
SelectComponents[vDilate @ #,  #AdjacentBorders == {Bottom, Top} &],
{ "BoundingBox", "CaliperWidth"}, "Dataset"] &;

dataSet2 /@ {img1, img2, img3}


• Overly extended answer with far too many updates, for the sake of clarity, please post only what you think is the correct answer. – Pedro Lobito Jun 11 '20 at 9:54
• It seems easy for some command writers. Dear, as I said: "I have no knowledge in mathematica, python, and other great softwares." Well, where should I paste the code you provided? Possible in mathematica online? or I must download it? Can I use that code for around $10,000$ pictures, .. Please dear, I need you to consider my knowledge. What I need is a very simple procedure that I should do,like;(Download the software from "LINK". Open the "Window".Paste the provided code. Drag your pictures, click "Run". Provide some screenshot if possible my dear.That would be really really appreciated.(+1) – Hussain-Alqatari Jun 11 '20 at 15:38
• @Hussain-Alqatari, you can use the code with Mathematica Online. Go to the page wolframcloud.com and follow the instructions to open an account. Then copy/paste/evaluate the code in this post in a new notebook. – kglr Jun 11 '20 at 18:54
• @kglr Well, See this pic ibb.co/N7nN0m9 , am I doing right, pasting it in the right place? If right, what should I change now, like #AdjacentBorders and #CaliperWidth and "BoundingBox" and such things,, need to be changed? Ok, then where to upload the pictures to be processed. Please help me sir, your support is really appreciated. – Hussain-Alqatari Jun 11 '20 at 20:02
• @kglr I think (NOT SURE), I can continue now. Words are powerless to thank you, bounty after $7$ hours :). – Hussain-Alqatari Jun 12 '20 at 11:16

This is perhaps a less precise but very fast approach which returns reasonably good widths for these three images. It binarizes the images, and collapses all rows down onto a single row by adding them together and unitizing them. It then looks for runs of zeros in the middle of the row and returns the lengths. I take the Max of these lengths after I call the function on each image:

gapSizes[img_] :=
Module[{values, splits,
bimg = Binarize[MinFilter[img, 4], Method -> "Mean"], dsc},
(* remove small blobs with < 1% of the total pixel count *)
dsc = Round[0.01*Times @@ ImageDimensions[img]];
bimg = DeleteSmallComponents[bimg, dsc];
values = Unitize[Total[ImageData[bimg]]];
If[values[[1]] == 0, values = Drop[values, First@FirstPosition[values, 1] - 1]];
splits = Select[Split[values], #[[1]] == 0 &];
Length /@ If[splits[[-1, 1]] == 0, Most[splits], splits]]

imgs = Import /@ {"https://i.stack.imgur.com/kRjAA.jpg",
"https://i.stack.imgur.com/d0ALv.jpg",
"https://i.stack.imgur.com/W5Zcd.jpg",
"https://i.stack.imgur.com/b9M7d.jpg"};
Max[gapSizes[#]] & /@ imgs

(* returns {552, 15, 51, 140} *)


Note it is quite sensitive and images with low brightness areas will perform badly.

• It worked much much much faster than the previous code, also with reasonably good widths. Thanks a lot. BUT, for some images, like i.stack.imgur.com/b9M7d.jpg, it shows $173$ (NOT reasonably good width). See i.stack.imgur.com/Icrlx.png Almost $137-139$, not $173$. Can we improve this? THANKS A LOT! – Hussain-Alqatari Jun 14 '20 at 9:00
• I am still trying but could not know why for some images it shows (not) good width. It work fast and good for some images, that is really great job from you. I believe you can. – Hussain-Alqatari Jun 14 '20 at 11:36
• I've updated the answer. Some of your images have poor brightness on the leading edge of the right hand side. I don't think a one-size-fits-all solution exists without at least some manual cleanup of the images. – flinty Jun 14 '20 at 12:20