The most important thing in this problem is to be able to control the scene and more importantly:
- The lighting
- The camera position & angle
Simply put, the lighting is what creates the essential contrast to delineate the thread. If you light the thread "head on" (light from the viewpoint of the camera), there will be no contrast or "ups and downs" to count. The better position for the light is at the top and slightly forward of the face of the thread so that a) All thread peaks are lit (both those that are on the top, but also those that are below them) and b) The top thread casts a bit of shadow to the one below it. Try to think of the thread as a difraction grating, only here, you need the shadows.
The Camera: The camera distance from the thread and angle to the thread are very important, especially if you wish at some point to derive the pitch of the thread. To derive the pitch of the thread, you would have to count lines per unit of distance.
The Camera Distance: The only way to relate the pixel size to a physical unit of length in a monocular image, is to fix the distance between the subject and the lens, similarly to how they do it in archaeology photography. There, we assume that the ruler's plain coincides with the focus plane of the lens, or is too near to be creating a negligible error.
The Camera Angle: The camera angle is incredibly important in this problem because it is responsible for an optical distortion that would affect your measurement. In the nut example, it is impossible to image the thread vertically ("head on"), because you are trying to image the inside of a very small and thick cylinder. Therefore, you have to look at it from an angle. In the bolt example, it is impossible to image the thread vertically ("head on"), because there will be no shadows to create contrast. A pen, imaged at an angle will appear shorter than it really is. Similarly, the thread imaged at an angle, will not give you equidistant peaks. Specifically, as the thread "screws away" from the camera, its pitch will be increasing because the lines that denote the threads would appear to get closer and closer and closer. Therefore, the angle that the camera is set against the screw determines a) The error of measurement and b) the limits of measurement (how fine / coarse a thread you can measure).
AFTER determining / fixing these parameters which are very important to the problem you are dealing with, you can start thinking about extracting the thread. If you get the imaging conditions right, the thread extraction via image processing will be much more easier and straightforward than trying to do it over any random image.
Trivial way of extracting the thread:
Assuming that the thread is aligned and centred within the camera's Field Of View (FOV), you are using something like Octave and the image has been loaded in
I with something like
I = imread('someNutImage.jpg');:
- Threshold the image
- Because the thread is (supposedly) aligned with the FOV, take the middle column of values. That would look something like:
Notice here how the pitch changes visibly because of the camera angle.
Now, "count" the gaps. This is a trivial "pulse counting" excercise which I have outlined previously. Prior to counting the pulse widths, you need an absolutely saturated signal. Therefore
Q = double(I(:,320)>128).*255; (Where
320 is some column image, I am taking the middle here as explained above). Now, differentiate the signal to get the "pulse fronts" and then differentiate that again, to get the "inter-pulse-front" distances. That is:
pulse_fronts = find(abs(diff(Q)));
pulse_lengths = diff(pulse_fronts);
For the signal I am using here, I get:
1, 24, 64, 11, 8, 13, 6, 14, 6, 13, 6, 13, 6, 13, 5, 12, 6, 11, 5, 12, 5, 10, 6
That is, the signal starts with a spike pulse of length 1, followed by 24 zeroes followed by 11 ones, followed by 8 zeros, followed by 13 ones and so on.
Of course, you are not going to "count the gaps" manually. At this point, you can do a histogram and obtain the two most frequent values. For example, with a
hist(pulseLength,max(pulseLength)), you get something like:
The "low" most frequent value (6) are the zeroes and the "high" most frequent value (13) are the ones. Once you have these two values (from the histogram), you know the full length of your thread in pixels. To convert it to mm, you need to determine the pixel size.
Less trivial way of extracting the thread:
The above method allows you to filter (if required) the pulse lengths to get rid of various artifacts. Another way to extract the thread would be to use the Discrete Fourier Transform of
Q to assess the frequency of the pulse train.
semilogy(abs(fft(Q))), gives you something like:
This image is 480 pixels tall. This means that the highest frequency (in terms of alternating black/white lines per unit of length) is 240. The first peak of the DFT occurs at 26 lines. From the method above we had ~13+~6=~19. The DFT will give you an average pitch over the whole acquired signal. Again, to give these numbers a physical meaning, you would need to know the pixel size which is only known by fixing the distance from the camera.
Even less trivial way of extracting the thread:
Once you threshold the image, either apply thining or a simple high-pass convolution mask to preserve the edges and then a Hough Transform. This will generate one "dot" for every slanted "line" that is created by the thread. What you would be looking for then would be a series of dots, one next to the other signifying the thread. By measuring the distance between these dots, you can extract your thread information. This is probably the most accurate method because the Hough Transform is very sensitive and accurate at recovering lines from very noisy images like the thresholded one we are talking about here. It is also the most computationally expensive option.
Each one of these methods has pros and cons for estimating the thread of a nut or bolt.
Finally, the most important part in such applications is to fix the imaging geometry so that you know what you measure and what sort of limits this measurement has.
Hope this helps.