if gettickcount-rendertickcount >= 33thenbegin// 1000 / 33 = ~30 FPS //... rendertickcount := gettickcount; cc := cvWaitKey(1); end;
I will describe the algorithm itself. For each sample from the base, an “album” of rotated samples was generated in 10-degree increments. This makes it possible to store much less samples in the database of standards and not to waste resources on rotation “on the fly”. I implement the primitive perspective correction on the fly using cvResize.
As a result of the sliding of nuts along the grooves, they quickly stain these same grooves with fat, which is very rich. This fact prevents more accurate finding of the contours of nuts. I tried both simple cvThreshold and cvThreshold with cvCanny on top - it worked poorly on a dirty background. Plus, the shadow, which was thrown by the nuts, hindered when spans at a small distance from the background. To solve this problem, I came up with my filter. Its essence is that it replaces the most "non-colored" pixels with white pixels.
Show
procedureremoveBack(var img: PIplImage; k:integer);var x, y :integer; sat: byte; framesize :integer; begin cvcvtColor(img, hsv, CV_BGR2HSV); x := 1; framesize := img.width * img.height * 3; while x <= framesize dobegin sat := hsv.imageData[x]; if sat < k thenbegin hsv.imageData[x-1] := 255; hsv.imageData[x+1] := 255; hsv.imageData[x] := 0; end; inc(x ,3); end; cvcvtColor(hsv, img, CV_HSV2BGR); end;
For nuts sliding on a white background, there is a contour. A mask is made from the contour, which allows you to copy each nut with transparency into an array of PIplImage. Too small and very large contours are skipped.
The frame is divided into regions-> lines, in reality, these are separate gutters, along which nuts slide. At the end of each line there is an actuator, which is a nozzle that controls the flow of air under pressure. In the application, each line is serviced by a separate thread (thread). Inside the thread, we find the nut closest to the nozzle, and determine its “similarity” with the base of the reference samples. Below is a code section that considers “similarity” via cvAbsDiff:
The value of the wcount variable is the coefficient of similarity between the nut and the standard in “parrots”. When this value is exceeded above the threshold, we transmit the line number through a com port in arduino. The controller opens the nozzle for a specified time, than “blows” the nut, in the normal state, the nozzle is closed. For asynchronous operation of executive devices, the following sketch was written.
Show
int timeout = 75; int comm; unsigned long timeStamps[8]; int ePins[] = {2, 3, 4, 5, 6, 7, 8, 9}; void setup() { for (int i=0; i <= 7; i++){ pinMode(ePins[i], OUTPUT); } Serial.begin(9600); while (!Serial) { ; // wait forserial port toconnect. Needed for Leonardo only } } voidloop() { if (Serial.available() > 0) { comm = Serial.read(); if (comm >= 0 && comm <= 7) { digitalWrite(ePins[comm], HIGH); timeStamps[comm] = millis(); } if (comm == 66) { Serial.write(103); // for device autodetection, 103 means version1.03 } } for (int i=0; i <= 7; i++){ if (millis() - timeStamps[i] >= timeout) { digitalWrite(ePins[i], LOW); } } }
Nozzles are an electromagnetic solenoid. We switch this load according to the following scheme. Each nozzle needs a separate key.
Show
And this is the assembled device:
Show
At the request of the customer, I can not publish images of the finished device. I hope the next video will provide an opportunity to present the final device.
I tried to describe the most difficult and interesting moments that I met as a result of working on this interesting project. Feel free to ask questions if, in your opinion, something is not detailed enough. Thanks for attention.