สารบัญ:
วีดีโอ: Robotic Bead Sorting: 3 ขั้นตอน (พร้อมรูปภาพ)
2024 ผู้เขียน: John Day | [email protected]. แก้ไขล่าสุด: 2024-01-30 13:06
ในโครงการนี้ เราจะสร้างหุ่นยนต์เพื่อคัดแยกลูกปัด Perler ตามสี
ฉันต้องการสร้างหุ่นยนต์คัดแยกสีมาโดยตลอด ดังนั้นเมื่อลูกสาวของฉันสนใจงานประดิษฐ์ลูกปัด Perler ฉันเห็นว่านี่เป็นโอกาสที่สมบูรณ์แบบ
ลูกปัด Perler ถูกใช้เพื่อสร้างโปรเจ็กต์ศิลปะแบบหลอมรวมโดยวางลูกปัดจำนวนมากไว้บนกระดานหมุด แล้วหลอมเข้าด้วยกันด้วยเหล็ก คุณมักจะซื้อลูกปัดเหล่านี้ในชุดลูกปัดคละสีขนาดยักษ์ 22,000 เม็ด และใช้เวลามากในการค้นหาสีที่คุณต้องการ ดังนั้นฉันคิดว่าการจัดเรียงลูกปัดจะช่วยเพิ่มประสิทธิภาพทางศิลปะ
ฉันทำงานให้กับ Phidgets Inc. ดังนั้นฉันจึงใช้ Phidgets เป็นส่วนใหญ่สำหรับโครงการนี้ แต่สามารถทำได้โดยใช้ฮาร์ดแวร์ที่เหมาะสม
ขั้นตอนที่ 1: ฮาร์ดแวร์
นี่คือสิ่งที่ฉันเคยสร้างสิ่งนี้ ฉันสร้างมันขึ้นมา 100% ด้วยชิ้นส่วนจาก phidgets.com และสิ่งต่างๆ ที่ฉันมีอยู่ในบ้าน
Phidgets บอร์ด, มอเตอร์, ฮาร์ดแวร์
- HUB0000 - VINT Hub Phidget
- 1108 - เซนเซอร์แม่เหล็ก
- 2x STC1001 - 2.5A Stepper Phidget
- 2x 3324 - 42STH38 NEMA-17 Bipolar Gearless Stepper
- 3x 3002 - ฟิดเจ็ตเคเบิ้ล 60ซม.
- 3403 - ฮับ USB2.0 4 พอร์ต
- 3031 - ผมเปียตัวเมีย 5.5x2.1mm
- 3029 - สายเคเบิลบิดเกลียว 100' 2 เส้น
- 3604 - ไฟ LED สีขาว 10 มม. (ถุงละ 10 ชิ้น)
- 3402 - เว็บแคม USB
ส่วนอื่นๆ
- แหล่งจ่ายไฟ 24VDC 2.0A
- เศษไม้และโลหะจากโรงรถ
- ซิปรูด
- ภาชนะพลาสติกที่มีการตัดด้านล่าง
ขั้นตอนที่ 2: ออกแบบหุ่นยนต์
เราจำเป็นต้องออกแบบบางสิ่งที่สามารถนำลูกปัดชิ้นเดียวออกจากถาดป้อนกระดาษ วางไว้ใต้เว็บแคม แล้วย้ายไปยังถังขยะที่เหมาะสม
กระบะลูกปัด
ฉันตัดสินใจทำส่วนที่ 1 ด้วยไม้อัดกลม 2 ชิ้น โดยแต่ละอันเจาะรูในที่เดียวกัน ชิ้นส่วนด้านล่างได้รับการแก้ไข และชิ้นส่วนบนติดกับสเต็ปเปอร์มอเตอร์ ซึ่งสามารถหมุนได้ภายใต้ถังบรรจุลูกปัด เมื่อรูเคลื่อนที่ใต้ถังพัก มันจะหยิบลูกปัดขึ้นมาชิ้นเดียว จากนั้นฉันสามารถหมุนมันภายใต้เว็บแคม แล้วหมุนต่อไปจนกระทั่งมันตรงกับรูที่ส่วนล่างสุด จากนั้นมันจะตกลงมา
ในภาพนี้ ฉันกำลังทดสอบว่าระบบสามารถทำงานได้ ทุกอย่างได้รับการแก้ไขยกเว้นแผ่นไม้อัดกลมด้านบนซึ่งติดอยู่กับสเต็ปเปอร์มอเตอร์โดยมองไม่เห็นด้านล่าง เว็บแคมยังไม่ได้ติดตั้ง ฉันแค่ใช้ Phidget Control Panel เพื่อเปลี่ยนเป็นมอเตอร์ ณ จุดนี้
ที่เก็บลูกปัด
ส่วนต่อไปคือการออกแบบระบบถังเก็บสีแต่ละสี ฉันตัดสินใจใช้สเต็ปเปอร์มอเตอร์ตัวที่สองด้านล่างเพื่อรองรับและหมุนภาชนะทรงกลมที่มีช่องเว้นระยะห่างเท่ากัน สามารถใช้หมุนช่องที่ถูกต้องใต้รูที่ลูกปัดจะหลุดออกมาได้
ฉันสร้างสิ่งนี้โดยใช้กระดาษแข็งและเทปพันท่อ สิ่งที่สำคัญที่สุดที่นี่คือความสม่ำเสมอ - แต่ละช่องควรมีขนาดเท่ากัน และสิ่งของทั้งหมดควรมีน้ำหนักเท่ากันเพื่อให้หมุนได้โดยไม่ข้าม
การถอดลูกปัดทำได้โดยใช้ฝาปิดที่แน่นซึ่งจะเปิดช่องเดียวในแต่ละครั้ง จึงสามารถเทลูกปัดออกได้
กล้อง
เว็บแคมติดตั้งอยู่บนเพลทด้านบนระหว่างฮ็อปเปอร์กับตำแหน่งรูเพลทด้านล่าง ซึ่งช่วยให้ระบบมองลูกปัดก่อนทำหล่น ไฟ LED ใช้เพื่อส่องสว่างลูกปัดที่อยู่ใต้กล้อง และแสงโดยรอบจะถูกปิดกั้น เพื่อให้มีสภาพแวดล้อมแสงที่สม่ำเสมอ นี่เป็นสิ่งสำคัญมากสำหรับการตรวจจับสีที่แม่นยำ เนื่องจากแสงโดยรอบสามารถทำให้สีที่รับรู้ได้หายไป
การตรวจจับตำแหน่ง
ระบบจะต้องสามารถตรวจจับการหมุนของตัวคั่นลูกปัดได้เป็นสิ่งสำคัญ ใช้เพื่อกำหนดตำแหน่งเริ่มต้นเมื่อสตาร์ทเครื่อง แต่ยังใช้เพื่อตรวจสอบว่าสเต็ปเปอร์มอเตอร์ไม่ซิงค์หรือไม่ ในระบบของฉัน บางครั้งลูกปัดอาจติดขัดขณะหยิบขึ้นมา และระบบจำเป็นต้องสามารถตรวจจับและจัดการกับสถานการณ์นี้ได้ - โดยสำรองข้อมูลเล็กน้อยและลองใช้แบบ Agian
มีหลายวิธีในการจัดการกับสิ่งนี้ ฉันตัดสินใจใช้เซ็นเซอร์แม่เหล็ก 1108 โดยมีแม่เหล็กฝังอยู่ที่ขอบแผ่นด้านบน ซึ่งช่วยให้ฉันตรวจสอบตำแหน่งในการหมุนทุกครั้งได้ ทางออกที่ดีกว่าน่าจะเป็นตัวเข้ารหัสบนสเต็ปเปอร์มอเตอร์ แต่ฉันมี 1108 วางอยู่รอบ ๆ ดังนั้นฉันจึงใช้มัน
จบหุ่นยนต์
ณ จุดนี้ทุกอย่างได้รับการดำเนินการและทดสอบแล้ว ได้เวลาเมานต์ทุกอย่างอย่างสวยงามและย้ายไปเขียนซอฟต์แวร์
สเต็ปเปอร์มอเตอร์ 2 ตัวถูกขับเคลื่อนด้วยคอนโทรลเลอร์สเต็ป STC1001 HUB000 - ฮับ USB VINT ใช้สำหรับเรียกใช้ตัวควบคุมแบบสเต็ป เช่นเดียวกับการอ่านเซ็นเซอร์แม่เหล็กและขับ LED เว็บแคมและ HUB0000 ต่ออยู่กับฮับ USB ขนาดเล็ก ผมเปีย 3031 และสายไฟบางส่วนใช้ร่วมกับแหล่งจ่ายไฟ 24V เพื่อจ่ายไฟให้กับมอเตอร์
ขั้นตอนที่ 3: เขียนโค้ด
C# และ Visual Studio 2015 ใช้สำหรับโปรเจ็กต์นี้ ดาวน์โหลดแหล่งที่มาที่ด้านบนของหน้านี้และปฏิบัติตาม - ส่วนหลักจะระบุไว้ด้านล่าง
การเริ่มต้น
อันดับแรก เราต้องสร้าง เปิด และเริ่มต้นวัตถุ Phidget สิ่งนี้ทำในเหตุการณ์การโหลดแบบฟอร์มและตัวจัดการแนบ Phidget
โมฆะส่วนตัว Form1_Load (ผู้ส่งวัตถุ EventArgs e) {
/* เริ่มต้นและเปิด Phidgets */
top. HubPort = 0; top. Attach += Top_Attach; top. Detach += Top_Detach; top. PositionChange += Top_PositionChange; ด้านบนเปิด ();
bottom. HubPort = 1;
bottom. Attach += Bottom_Attach; bottom. Detach += Bottom_Detach; bottom. PositionChange += Bottom_PositionChange; ด้านล่างเปิด ();
magSensor. HubPort = 2;
magSensor. IsHubPortDevice = true; magSensor. Attach += MagSensor_Attach; magSensor. Detach += MagSensor_Detach; magSensor. SensorChange += MagSensor_SensorChange; magSensor. Open();
led. HubPort = 5;
led. IsHubPortDevice = true; นำช่อง = 0; led. Attach += Led_Attach; led. Detach += Led_Detach; led.เปิด(); }
โมฆะส่วนตัว Led_Attach (ผู้ส่งวัตถุ Phidget22. Events. AttachEventArgs e) {
ledAttachedChk. Checked = จริง; led. State = จริง; ledChk. Checked = จริง; }
โมฆะส่วนตัว MagSensor_Attach (ผู้ส่งวัตถุ Phidget22. Events. AttachEventArgs e) {
magSensorAttachedChk. Checked = true; magSensor. SensorType = VoltageRatioSensorType. PN_1108; magSensor. DataInterval = 16; }
โมฆะส่วนตัว Bottom_Attach (ผู้ส่งวัตถุ Phidget22. Events. AttachEventArgs e) {
bottomAttachedChk. Checked = จริง; bottom. CurrentLimit = bottomCurrentLimit; bottom. Engaged = จริง; bottom. VelocityLimit = ด้านล่าง VelocityLimit; bottom. Acceleration = bottomAccel; bottom. DataInterval = 100; }
โมฆะส่วนตัว Top_Attach (ผู้ส่งวัตถุ Phidget22. Events. AttachEventArgs e) {
topAttachedChk. Checked = จริง; top. CurrentLimit = topCurrentLimit; top. Engaged = จริง; top. RescaleFactor = -1; top. VelocityLimit = -topVelocityLimit; top. Acceleration = -topAccel; top. DataInterval = 100; }
เรายังอ่านข้อมูลสีที่บันทึกไว้ในระหว่างการเริ่มต้น เพื่อให้สามารถดำเนินการก่อนหน้านี้ต่อไปได้
การวางตำแหน่งมอเตอร์
รหัสการจัดการมอเตอร์ประกอบด้วยฟังก์ชันอำนวยความสะดวกสำหรับการเคลื่อนย้ายมอเตอร์ มอเตอร์ที่ฉันใช้คือ 3, 200 1/16 ขั้นต่อรอบ ดังนั้นฉันจึงสร้างค่าคงที่สำหรับสิ่งนี้
สำหรับมอเตอร์ตัวบน มี 3 ตำแหน่งที่เราต้องการให้สามารถส่งไปยังมอเตอร์ได้: เว็บแคม รู และแม่เหล็กกำหนดตำแหน่ง มีฟังก์ชั่นสำหรับการเดินทางไปยังแต่ละตำแหน่งเหล่านี้:
โมฆะส่วนตัว nextMagnet (บูลีนรอ = เท็จ) {
double posn = ด้านบน ตำแหน่ง % stepsPerRev;
top. TargetPosition += (stepsPerRev - posn);
ถ้า (รอ)
ในขณะที่ (top. IsMoving) Thread. Sleep(50); }
โมฆะส่วนตัว nextCamera (บูลีนรอ = เท็จ) {
double posn = ด้านบน ตำแหน่ง % stepsPerRev; ถ้า (posn < Properties. Settings. Default.cameraOffset) top. TargetPosition += (Properties. Settings. Default.cameraOffset - posn); อื่น top. TargetPosition += ((Properties. Settings. Default.cameraOffset - posn) + stepsPerRev);
ถ้า (รอ)
ในขณะที่ (top. IsMoving) Thread. Sleep(50); }
โมฆะส่วนตัว nextHole (รอบูลีน = เท็จ) {
double posn = ด้านบน ตำแหน่ง % stepsPerRev; ถ้า (posn < Properties. Settings. Default.holeOffset) top. TargetPosition += (Properties. Settings. Default.holeOffset - posn); อื่น top. TargetPosition += ((Properties. Settings. Default.holeOffset - posn) + stepsPerRev);
ถ้า (รอ)
ในขณะที่ (top. IsMoving) Thread. Sleep(50); }
ก่อนเริ่มวิ่ง แผ่นปิดด้านบนจะถูกจัดตำแหน่งโดยใช้เซ็นเซอร์แม่เหล็ก สามารถเรียกใช้ฟังก์ชัน alignMotor เพื่อจัดตำแหน่งเพลทด้านบนได้ตลอดเวลา ฟังก์ชันนี้จะเปลี่ยนเพลทเป็น 1 รอบอย่างรวดเร็วก่อนอย่างรวดเร็ว จนกว่าจะเห็นข้อมูลแม่เหล็กอยู่เหนือขีดจำกัด จากนั้นสำรองข้อมูลเล็กน้อยและเคลื่อนไปข้างหน้าอีกครั้งอย่างช้าๆ โดยจะบันทึกข้อมูลเซ็นเซอร์ขณะดำเนินการ สุดท้าย จะกำหนดตำแหน่งเป็นตำแหน่งข้อมูลแม่เหล็กสูงสุด และรีเซ็ตตำแหน่งออฟเซ็ตเป็น 0 ดังนั้น ตำแหน่งแม่เหล็กสูงสุดควรอยู่ที่ (top. Position % stepsPerRev)
การจัดตำแหน่งเกลียว มอเตอร์ เกลียว เลื่อยบูลีนแม่เหล็ก; magSensorMax สองเท่า = 0; โมฆะส่วนตัว alignMotor () {
//หาแม่เหล็ก
top. DataInterval = top. MinDataInterval;
sawMagnet = เท็จ;
magSensor. SensorChange += magSensorStopMotor; ขีด จำกัด ความเร็ว = -1000;
int tryCount = 0;
ลองอีกครั้ง:
top. TargetPosition += stepsPerRev;
ในขณะที่ (top. IsMoving && !sawMagnet) Thread. Sleep(25);
ถ้า (!sawMagnet) {
if (tryCount > 3) { Console. WriteLine ("การจัดแนวล้มเหลว"); top. Engaged = เท็จ; bottom. Engaged = เท็จ; runtest = เท็จ; กลับ; }
tryCount++;
Console. WriteLine("เราติดอยู่หรือเปล่า กำลังพยายามสำรองข้อมูล…"); top. TargetPosition -= 600; ในขณะที่ (top. IsMoving) Thread. Sleep(100);
ไปลองอีกครั้ง;
}
ขีด จำกัด ความเร็ว = -100;
magData = รายการใหม่>(); magSensor. SensorChange += magSensorCollectPositionData; top. TargetPosition += 300; ในขณะที่ (top. IsMoving) Thread. Sleep(100);
magSensor. SensorChange -= magSensorCollectPositionData;
top. VelocityLimit = -topVelocityLimit;
KeyValuePair สูงสุด = magData[0];
foreach (คู่ KeyValuePair ใน magData) ถ้า (pair. Value > max. Value) max = คู่;
top. AddPositionOffset(-max. Key);
magSensorMax = max. Value;
top. TargetPosition = 0;
ในขณะที่ (top. IsMoving) Thread. Sleep(100);
Console. WriteLine("จัดแนวสำเร็จ");
}
รายการ> magData;
โมฆะส่วนตัว magSensorCollectPositionData (ผู้ส่งวัตถุ, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) { magData. Add (ใหม่ KeyValuePair (ตำแหน่งบนสุด, e. SensorValue)); }
โมฆะส่วนตัว magSensorStopMotor (ผู้ส่งวัตถุ, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {
ถ้า (top. IsMoving && e. SensorValue > 5) { top. TargetPosition = top. Position - 300; magSensor. SensorChange -= magSensorStopMotor; sawMagnet = จริง; } }
สุดท้าย มอเตอร์ด้านล่างถูกควบคุมโดยส่งไปยังตำแหน่งใดตำแหน่งหนึ่งที่บรรจุเม็ดบีด สำหรับโครงการนี้ เรามี 19 ตำแหน่ง อัลกอริทึมกำลังเลือกเส้นทางที่สั้นที่สุด และหมุนตามเข็มนาฬิกาหรือทวนเข็มนาฬิกา
int BottomPosition ส่วนตัว { รับ { int posn = (int)bottom. Position % stepsPerRev; ถ้า (posn < 0) posn += stepsPerRev;
ผลตอบแทน (int)Math. Round(((posn * beadCompartments) / (double)stepsPerRev));
} }
โมฆะส่วนตัว SetBottomPosition (int posn, bool wait = false) {
posn = posn % ช่องลูกปัด; double targetPosn = (posn * stepsPerRev) / beadCompartments;
สองเท่า currentPosn = bottom. Position % stepsPerRev;
double posnDiff = targetPosn - currentPosn;
//เก็บไว้เป็นขั้นตอน
posnDiff = ((int)(posnDiff / 16)) * 16;
ถ้า (posnDiff <= 1600) bottom. TargetPosition += posnDiff; อื่นด้านล่าง. TargetPosition -= (stepsPerRev - posnDiff);
ถ้า (รอ)
ในขณะที่ (bottom. IsMoving) Thread. Sleep(50); }
กล้อง
OpenCV ใช้เพื่ออ่านภาพจากเว็บแคม เธรดกล้องเริ่มต้นก่อนที่จะเริ่มเธรดการเรียงลำดับหลัก เธรดนี้จะอ่านรูปภาพอย่างต่อเนื่อง คำนวณสีเฉลี่ยสำหรับพื้นที่เฉพาะโดยใช้ค่าเฉลี่ย และอัปเดตตัวแปรสีส่วนกลาง ด้ายยังใช้ HoughCircles เพื่อพยายามตรวจจับลูกปัดหรือรูในเพลทด้านบน เพื่อปรับแต่งพื้นที่ที่มองหาสำหรับการตรวจจับสี เกณฑ์และหมายเลข HoughCircles ถูกกำหนดผ่านการลองผิดลองถูก และขึ้นอยู่กับเว็บแคม การจัดแสง และระยะห่างอย่างมาก
bool runVideo = true; bool videoRunning = false; จับภาพวิดีโอ; เธรด cvThread; ตรวจพบสีสี; การตรวจจับบูลีน = เท็จ; int ตรวจจับ Cnt = 0;
โมฆะส่วนตัว cvThreadFunction () {
videoRunning = เท็จ;
จับภาพ = VideoCapture ใหม่ (selectedCamera);
ใช้ (หน้าต่างหน้าต่าง = หน้าต่างใหม่ ("จับภาพ")) {
ภาพเสื่อ = เสื่อใหม่ (); Mat image2 = เสื่อใหม่ (); ในขณะที่ (runVideo) { capture. Read (ภาพ); ถ้า (image. Empty()) แตก;
ถ้า (ตรวจจับ)
ตรวจจับCnt++; อื่นตรวจพบCnt = 0;
ถ้า (การตรวจจับ || circleDetectChecked || showDetectionImgChecked) {
Cv2. CvtColor (รูปภาพ, image2, ColorConversionCodes. BGR2GRAY); Mat thres = image2. Threshold ((double) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); thres = thres. GaussianBlur (ใหม่ OpenCvSharp. Size (9, 9), 10);
ถ้า (showDetectionImgChecked)
รูปภาพ = thres;
ถ้า (การตรวจจับ || circleDetectChecked) {
CircleSegment ลูกปัด = thres. HoughCircles(HoughMethods. Gradient, 2, /*thres. Rows/4*/ 20, 200, 100, 20, 65); if (bead. Length >= 1) { image. Circle(bead[0]. Center, 3, new Scalar(0, 100, 0), -1); image. Circle(bead[0]. Center, (int)bead[0]. Radius, ใหม่ Scalar(0, 0, 255), 3); ถ้า (ลูกปัด[0].รัศมี >= 55) { Properties. Settings. Default.x = (ทศนิยม)ลูกปัด[0]. Center. X + (ทศนิยม)(ลูกปัด[0].รัศมี / 2); Properties. Settings. Default.y = (ทศนิยม)ลูกปัด[0]. Center. Y - (ทศนิยม)(ลูกปัด[0].รัศมี / 2); } อื่น { Properties. Settings. Default.x = (ทศนิยม) ลูกปัด[0]. Center. X + (ทศนิยม) (ลูกปัด[0].รัศมี); Properties. Settings. Default.y = (ทศนิยม)ลูกปัด[0]. Center. Y - (ทศนิยม)(ลูกปัด[0].รัศมี); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } อื่น {
CircleSegment วงกลม = thres. HoughCircles(HoughMethods. Gradient, 2, /*thres. Rows/4*/ 5, 200, 100, 60, 180);
if (circles. Length > 1) { List xs = circles. Select(c => c. Center. X). ToList(); xs. เรียงลำดับ (); รายการ ys = circles. Select(c => c. Center. Y). ToList(); ys. เรียงลำดับ ();
int medianX = (int)xs[xs. Count / 2];
int ค่ามัธยฐานY = (int)ys[ys. Count / 2];
ถ้า (medianX > image. Width - 15)
ค่ามัธยฐานX = ภาพความกว้าง - 15; ถ้า (มัธยฐาน > image. Height - 15) มัธยฐาน = image. Height - 15;
image. Circle(medianX, medianY, 100, new Scalar(0, 0, 150), 3);
ถ้า (ตรวจจับ) {
Properties. Settings. Default.x = ค่ามัธยฐานX - 7; Properties. Settings. Default.y = ค่ามัธยฐาน - 7; Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } } } } }
Rect r = ใหม่ Rect ((int) Properties. Settings. Default.x, (int) Properties. Settings. Default.y, (int) Properties. Settings. Default.size, (int) Properties. Settings. Default.height);
Mat beadSample = เสื่อใหม่ (รูปภาพ, r);
Scalar avgColor = Cv2. Mean (ตัวอย่างลูกปัด); ตรวจพบสี = Color. FromArgb((int) avgColor[2], (int) avgColor[1], (int) avgColor[0]);
image. Rectangle(r, ใหม่ Scalar(0, 150, 0));
window. ShowImage (ภาพ);
Cv2. WaitKey(1); videoRunning = จริง; }
videoRunning = เท็จ;
} }
กล้องโมฆะส่วนตัวStartBtn_Click (ผู้ส่งวัตถุ EventArgs e) {
ถ้า (cameraStartBtn. Text == "start") {
cvThread = เธรดใหม่ (ThreadStart ใหม่ (cvThreadFunction)); runVideo = จริง; cvThread. Start(); cameraStartBtn. Text = "หยุด"; ในขณะที่ (!videoRunning) Thread. Sleep(100);
updateColorTimer. Start();
} อื่น {
runVideo = เท็จ; cvThread. Join(); cameraStartBtn. Text = "เริ่มต้น"; } }
สี
ตอนนี้ เราสามารถกำหนดสีของลูกปัด และตัดสินใจตามสีนั้นว่าจะใส่ลงในภาชนะใด
ขั้นตอนนี้อาศัยการเปรียบเทียบสี เราต้องการแยกสีออกจากกันเพื่อจำกัดผลบวกลวง แต่ยังอนุญาตให้มีธรณีประตูที่เพียงพอเพื่อจำกัดผลลบลวง ที่จริงแล้วการเปรียบเทียบสีนั้นซับซ้อนอย่างน่าประหลาดใจ เพราะวิธีที่คอมพิวเตอร์เก็บสีเป็น RGB และวิธีที่มนุษย์รับรู้สีนั้นไม่มีความสัมพันธ์แบบเส้นตรง ที่เลวร้ายไปกว่านั้น ยังต้องคำนึงถึงสีของแสงที่อยู่ภายใต้สีนั้นด้วย
มีอัลกอริธึมที่ซับซ้อนสำหรับการคำนวณความแตกต่างของสี เราใช้ CIE2000 ซึ่งให้ผลลัพธ์เป็นตัวเลขใกล้ 1 ถ้า 2 สีไม่สามารถแยกแยะได้สำหรับมนุษย์ เรากำลังใช้ไลบรารี ColorMine C# เพื่อทำการคำนวณที่ซับซ้อนเหล่านี้ พบว่าค่า DeltaE เท่ากับ 5 ให้การประนีประนอมที่ดีระหว่างผลบวกเท็จและลบเท็จ
เนื่องจากมักจะมีสีมากกว่าคอนเทนเนอร์ ตำแหน่งสุดท้ายจึงถูกสงวนไว้เป็นถังขยะที่รับทั้งหมด โดยทั่วไปฉันตั้งค่าเหล่านี้ไว้เพื่อให้ทำงานแม้ว่าเครื่องจะผ่านไปครั้งที่สอง
รายการ
สี = รายการใหม่ (); รายการ colorPanels = รายการใหม่ (); รายการ colorsTxts = รายการใหม่ (); รายการ colorCnts = รายการใหม่ ();
const int numColorSpots = 18;
const int ไม่ทราบColorIndex = 18; int findColorPosition (สี c) {
Console. WriteLine("กำลังหาสี…");
var cRGB = Rgb ใหม่ ();
cRGB. R = c. R; cRGB. G = c. G; cRGB. B = c. B;
int bestMatch = -1;
แมตช์คู่เดลต้า = 100;
สำหรับ (int i = 0; i < colors. Count; i++) {
var RGB = Rgb ใหม่ ();
RGB. R = สี. R; RGB. G = สี. G; RGB. B = สี. B;
เดลต้าคู่ = cRGB. Compare(RGB, CieDe2000Comparison() ใหม่);
//เดลต้าคู่ = deltaE(c, colors); Console. WriteLine("DeltaE (" + i. ToString() + "): " + delta. ToString()); ถ้า (เดลต้า <matchDelta) { matchDelta = เดลต้า; bestMatch = ผม; } }
if (matchDelta < 5) { Console. WriteLine ("พบแล้ว! (Posn: " + bestMatch + " Delta: " + matchDelta + ")"); ส่งคืน bestMatch; }
ถ้า (colors. Count < numColorSpots) { Console. WriteLine ("สีใหม่!"); สีเพิ่ม (c); this. BeginInvoke (การกระทำใหม่ (setBackColor) วัตถุใหม่ { colors. Count - 1 }); writeOutColors(); ผลตอบแทน (colors. Count - 1); } อื่น ๆ { Console. WriteLine ("ไม่ทราบสี!"); ส่งคืนไม่ทราบColorIndex; } }
ตรรกะการเรียงลำดับ
ฟังก์ชันการจัดเรียงจะรวบรวมชิ้นส่วนทั้งหมดเพื่อจัดเรียงลูกปัด ฟังก์ชันนี้ทำงานในเธรดเฉพาะ ย้ายเพลทด้านบน ตรวจจับสีลูกปัด วางลงในถังขยะ ตรวจสอบให้แน่ใจว่าเพลทด้านบนอยู่ในแนวเดียวกัน นับลูกปัด ฯลฯนอกจากนี้ยังหยุดทำงานเมื่อถังขยะเต็ม - ไม่เช่นนั้นเราจะจบลงด้วยลูกปัดล้น
เธรด colourTestThread;บูลีน runtest = false; เป็นโมฆะ colorTest () {
ถ้า (!top. Engaged)
top. Engaged = จริง;
ถ้า (!bottom. Engaged)
bottom. Engaged = จริง;
ในขณะที่ (รันเทส) {
ถัดไปแม่เหล็ก(จริง);
กระทู้.นอน(100); ลอง { ถ้า (magSensor. SensorValue < (magSensorMax - 4)) alignMotor(); } จับ { alignMotor (); }
nextCamera(จริง);
การตรวจจับ = จริง;
ในขณะที่ (detectCnt < 5) Thread. Sleep(25); Console. WriteLine("ตรวจจับจำนวน:" + detectCnt); การตรวจจับ = เท็จ;
สี c = ตรวจพบสี;
this. BeginInvoke (การกระทำใหม่ (setColorDet), วัตถุใหม่ { c }); int i = findColorPosition(c);
SetBottomPosition(i, จริง);
nextHole(จริง); colorCnts++; this. BeginInvoke (การกระทำใหม่ (setColorTxt), วัตถุใหม่ { i }); เธรดการนอนหลับ (250);
ถ้า (colorCnts [unknownColorIndex] > 500) {
top. Engaged = เท็จ; bottom. Engaged = เท็จ; runtest = เท็จ; this. BeginInvoke (การกระทำใหม่ (setGoGreen), null); กลับ; } } }
โมฆะส่วนตัว colourTestBtn_Click (ผู้ส่งวัตถุ EventArgs e) {
ถ้า (colourTestThread == null || !colourTestThread. IsAlive) { colourTestThread = เธรดใหม่ (ThreadStart ใหม่ (colourTest)); runtest = จริง; colourTestThread. Start(); colourTestBtn. Text = "หยุด"; colourTestBtn. BackColor = สีแดง; } อื่น ๆ { runtest = false; colourTestBtn. Text = "ไป"; colourTestBtn. BackColor = Color. Green; } }
ณ จุดนี้ เรามีโปรแกรมการทำงาน โค้ดบางส่วนหายไปจากบทความ ดังนั้นให้ดูที่ซอร์สโค้ดเพื่อเรียกใช้งานจริง
รางวัลรองชนะเลิศการประกวดเลนส์
แนะนำ:
ASL Robotic Hand (ซ้าย): 9 ขั้นตอน (พร้อมรูปภาพ)
ASL Robotic Hand (ซ้าย): โครงการในภาคการศึกษานี้คือการสร้างมือซ้ายที่พิมพ์ด้วยหุ่นยนต์สามมิติ ซึ่งสามารถแสดงตัวอักษรภาษามือแบบอเมริกันสำหรับผู้พิการทางการได้ยินในห้องเรียน การเข้าถึงเพื่อสาธิต American Sign Langu
Working Sorting Hat จาก Harry Potter: 8 ขั้นตอน
Working Sorting Hat จาก Harry Potter: ในโลกมักเกิ้ลของเรา ไม่มีหมวกวิเศษที่จะแยกเราเข้าไปในบ้านของเรา เลยใช้โอกาสกักตัวนี้ทำหมวกคัดแยก
Gesture Control Skeleton Bot - 4WD Hercules Mobile Robotic Platform - Arduino IDE: 4 ขั้นตอน (พร้อมรูปภาพ)
Gesture Control Skeleton Bot - 4WD Hercules Mobile Robotic Platform - Arduino IDE: ยานพาหนะควบคุมท่าทางที่ทำโดย Seeedstudio Skeleton Bot - 4WD Hercules Mobile Robotic Platform มีความสนุกสนานมากมายในช่วงการจัดการการแพร่ระบาดของโรคหลอดเลือดหัวใจที่บ้าน เพื่อนของฉันให้ 4WD Hercules Mobile Robotic Platform แก่ฉันเหมือนคุณ
LittleBits Magical Marble Sorting Machine: 11 ขั้นตอน (พร้อมรูปภาพ)
LittleBits Magical Marble Sorting Machine: คุณเคยต้องการจัดเรียงลูกหินหรือไม่ จากนั้นคุณสามารถสร้างเครื่องนี้ได้ คุณจะไม่ต้องสับเปลี่ยนถุงลูกแก้วอีกต่อไป! เป็นเครื่องคัดแยกหินอ่อนมหัศจรรย์ โดยใช้เซ็นเซอร์สีจาก Adafruit พิมพ์ TCS34725 และ Leonardo Arduino จาก
PhantomX Pincher Color Sorting: 4 ขั้นตอน
PhantomX Pincher Color Sorting: บทนำคำสั่งนี้จัดทำโดยนักเรียน 2 Automation Engineering จาก UCN (เดนมาร์ก) คำแนะนำแสดงให้เห็นว่าใครสามารถใช้ PhantomX Pncher เพื่อจัดเรียงกล่องตามสีด้วยการใช้ CMUcam5 Pixy แล้ววางซ้อนกัน แอปพลิเคชั่นนี้