สารบัญ:

Robotic Bead Sorting: 3 ขั้นตอน (พร้อมรูปภาพ)
Robotic Bead Sorting: 3 ขั้นตอน (พร้อมรูปภาพ)

วีดีโอ: Robotic Bead Sorting: 3 ขั้นตอน (พร้อมรูปภาพ)

วีดีโอ: Robotic Bead Sorting: 3 ขั้นตอน (พร้อมรูปภาพ)
วีดีโอ: วิธีทำตู้กดลูกอม 3 ช่อง จากลังกระดาษ | How to Make Candy Vending Machine from Cardboard 2024, กรกฎาคม
Anonim
Image
Image
หุ่นยนต์คัดแยกลูกปัด
หุ่นยนต์คัดแยกลูกปัด
หุ่นยนต์คัดแยกลูกปัด
หุ่นยนต์คัดแยกลูกปัด
หุ่นยนต์คัดแยกลูกปัด
หุ่นยนต์คัดแยกลูกปัด

ในโครงการนี้ เราจะสร้างหุ่นยนต์เพื่อคัดแยกลูกปัด 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: เขียนโค้ด

Image
Image

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; } }

ณ จุดนี้ เรามีโปรแกรมการทำงาน โค้ดบางส่วนหายไปจากบทความ ดังนั้นให้ดูที่ซอร์สโค้ดเพื่อเรียกใช้งานจริง

การประกวดเลนส์
การประกวดเลนส์

รางวัลรองชนะเลิศการประกวดเลนส์

แนะนำ: