Sadržaj:
Video: Robotsko sortiranje perlica: 3 koraka (sa slikama)
2024 Autor: John Day | [email protected]. Zadnja izmjena: 2024-01-30 08:07
U ovom projektu izgradit ćemo robota za sortiranje Perlerovih perlica po boji.
Oduvijek sam želio izgraditi robota za sortiranje boja, pa kad se moja kći zainteresirala za izradu Perler perlica, vidio sam ovo kao savršenu priliku.
Perlerove perle koriste se za stvaranje spojenih umjetničkih projekata postavljanjem mnogo perlica na ploču s pločama, a zatim ih istopiti peglom. Obično kupujete ove perle u ogromnim pakovanjima od 22 000 perlica u različitim bojama i trošite puno vremena na traženje boje koju želite, pa sam mislio da će njihovo sortiranje povećati umjetničku efikasnost.
Radim za Phidgets Inc. pa sam za ovaj projekt uglavnom koristio Phidgets - ali to se može učiniti pomoću bilo kojeg prikladnog hardvera.
Korak 1: Hardver
Evo šta sam koristio da ovo napravim. Napravio sam ga 100% sa dijelovima sa stranice phidgets.com i stvarima koje sam imao po kući.
Ploče Phidgets, Motori, Hardver
- HUB0000 - VINT Hub Phidget
- 1108 - Magnetski senzor
- 2x STC1001 - 2.5A Stepper Phidget
- 2x 3324 - 42STH38 NEMA -17 Bipolarni stepen bez zupčanika
- 3x 3002 - Phidget kabel 60 cm
- 3403 - USB2.0 4 -portno čvorište
- 3031 - Ženski pigtail 5.5x2.1mm
- 3029 - 2 -žilni 100 'upleteni kabel
- 3604 - 10 mm bijela LED dioda (vreća od 10)
- 3402 - USB web kamera
Ostali dijelovi
- Napajanje 24VDC 2.0A
- Stari otpad i metal iz garaže
- Zip kravate
- Plastična posuda s odrezanim dnom
Korak 2: Dizajnirajte robota
Moramo dizajnirati nešto što može uzeti jednu kuglicu iz ulaznog lijevka, postaviti je ispod web kamere, a zatim je premjestiti u odgovarajuću korpu.
Preuzimanje perli
Odlučio sam napraviti prvi dio s 2 komada okrugle šperploče, svaki s rupom izbušenom na istom mjestu. Donji dio je fiksiran, a gornji dio je pričvršćen za koračni motor, koji ga može rotirati ispod lijevka ispunjenog kuglicama. Kad rupa prođe ispod lijevka, uhvati jednu kuglicu. Zatim ga mogu rotirati ispod web kamere, a zatim dalje rotirati dok se ne poklapa s rupom na donjem dijelu, u kojem trenutku prolazi.
Na ovoj slici testiram da li sistem može raditi. Sve je fiksirano osim gornjeg okruglog komada šperploče, koji je pričvršćen na koračni motor ispod vidljivog polja. Web kamera još nije montirana. U ovom trenutku samo koristim Phidget Control Panel za okretanje motora.
Skladištenje perli
Sljedeći dio je dizajniranje sistema kanti za držanje svake boje. Odlučio sam upotrijebiti drugi koračni motor ispod za podršku i rotaciju okruglog spremnika s ravnomjerno raspoređenim odjeljcima. Ovo se može koristiti za okretanje odgovarajućeg odjeljka ispod rupe iz koje će perla ispasti.
Napravio sam ovo koristeći karton i ljepljivu traku. Najvažnija stvar ovdje je dosljednost - svaki odjeljak trebao bi biti iste veličine, a cijela bi stvar trebala biti podjednako ponderirana tako da se okreće bez preskakanja.
Uklanjanje perlica postiže se pomoću čvrsto zatvorenog poklopca koji izlaže jedan pretinac odjednom, tako da se perle mogu izliti.
Kamera
Web kamera je postavljena preko gornje ploče između lijevka i donje rupe. Ovo omogućava sistemu da pogleda zrno pre nego što ga spusti. LED se koristi za osvjetljavanje perlica ispod kamere, a ambijentalno svjetlo je blokirano, kako bi se osiguralo dosljedno svjetlosno okruženje. Ovo je vrlo važno za precizno otkrivanje boja, jer ambijentalno osvjetljenje zaista može odbaciti uočenu boju.
Otkrivanje lokacije
Važno je da sistem može otkriti rotaciju separatora zrna. Ovo se koristi za postavljanje početnog položaja pri pokretanju, ali i za otkrivanje da li se koračni motor nije sinkronizirao. U mom sistemu, zrnce će se ponekad zaglaviti dok se podiže, a sistem je morao biti u stanju da otkrije i reši ovu situaciju - tako što će napraviti malu rezervnu kopiju i isprobati.
Postoji mnogo načina za rješavanje ovoga. Odlučio sam koristiti magnetski senzor 1108, s magnetom ugrađenim u rub gornje ploče. To mi omogućava da provjerim položaj pri svakoj rotaciji. Bolje rješenje bi vjerojatno bio koder na koračnom motoru, ali imao sam 1108 koji je ležao pa sam to iskoristio.
Dovrši robota
U ovom trenutku sve je razrađeno i testirano. Vrijeme je da sve lijepo montirate i pređete na softver za pisanje.
Dva koračna motora pokreću stepenasti kontroleri STC1001. HUB000 - USB VINT čvorište koristi se za pokretanje koračnih kontrolera, kao i za čitanje magnetskog senzora i pokretanje LED diode. Web kamera i HUB0000 su spojeni na mali USB hub. Za napajanje motora koristi se 3031 pigtail i neka žica zajedno s napajanjem od 24V.
Korak 3: Napišite kôd
Za ovaj projekt koriste se C# i Visual Studio 2015. Preuzmite izvor na vrhu ove stranice i slijedite ga dalje - glavni odjeljci navedeni su u nastavku
Inicijalizacija
Prvo moramo stvoriti, otvoriti i pokrenuti Phidget objekte. To se radi u obliku učitavanja obrasca i rukovateljima za dodavanje Phidget -a.
private void Form1_Load (pošiljalac objekta, EventArgs e) {
/ * Inicirajte i otvorite Phidgets */
top. HubPort = 0; top. Attach += Top_Attach; top. Detach += Top_Detach; top. PositionChange += Top_PositionChange; top. Open ();
bottom. HubPort = 1;
bottom. Attach += Bottom_Attach; bottom. Detach += Bottom_Detach; bottom. PositionChange += Bottom_PositionChange; bottom. Open ();
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; led. Channel = 0; led. Attach += Led_Attach; led. Detach += Led_Detach; led. Open (); }
private void Led_Attach (pošiljalac objekta, Phidget22. Events. AttachEventArgs e) {
ledAttachedChk. Checked = true; led. State = istina; ledChk. Checked = true; }
private void MagSensor_Attach (pošiljalac objekta, Phidget22. Events. AttachEventArgs e) {
magSensorAttachedChk. Checked = true; magSensor. SensorType = VoltageRatioSensorType. PN_1108; magSensor. DataInterval = 16; }
private void Bottom_Attach (pošiljalac objekta, Phidget22. Events. AttachEventArgs e) {
bottomAttachedChk. Checked = true; bottom. CurrentLimit = bottomCurrentLimit; bottom. Engaged = true; bottom. VelocityLimit = bottomVelocityLimit; bottom. Acceleration = bottomAccel; bottom. DataInterval = 100; }
private void Top_Attach (pošiljalac objekta, Phidget22. Events. AttachEventArgs e) {
topAttachedChk. Checked = true; top. CurrentLimit = topCurrentLimit; top. Engaged = true; top. RescaleFactor = -1; top. VelocityLimit = -topVelocityLimit; top. Acceleration = -topAccel; top. DataInterval = 100; }
Tokom inicijalizacije čitamo i sve sačuvane podatke o boji, tako da se prethodni rad može nastaviti.
Pozicioniranje motora
Kôd za rukovanje motorom sastoji se od praktičnih funkcija za pomicanje motora. Motori koje sam koristio su 3, 200 1/16 koraka po okretu, pa sam za to stvorio konstantu.
Za gornji motor postoje 3 pozicije koje želimo poslati na motor: web kameru, rupu i magnet za pozicioniranje. Postoji funkcija za putovanje na svaku od ovih pozicija:
private void nextMagnet (Boolean wait = false) {
double posn = top. Position % stepsPerRev;
top. TargetPosition += (stepsPerRev - posn);
ako (čekaj)
while (top. IsMoving) Thread. Sleep (50); }
private void nextCamera (Boolean wait = false) {
double posn = top. Position % stepsPerRev; if (posn <Properties. Settings. Default.cameraOffset) top. TargetPosition += (Properties. Settings. Default.cameraOffset - posn); else top. TargetPosition + = ((Properties. Settings. Default.cameraOffset - posn) + stepsPerRev);
ako (čekaj)
while (top. IsMoving) Thread. Sleep (50); }
private void nextHole (Boolean wait = false) {
double posn = top. Position % stepsPerRev; if (posn <Properties. Settings. Default.holeOffset) top. TargetPosition += (Properties. Settings. Default.holeOffset - posn); else top. TargetPosition + = ((Properties. Settings. Default.holeOffset - posn) + stepsPerRev);
ako (čekaj)
while (top. IsMoving) Thread. Sleep (50); }
Prije početka trčanja gornja ploča se poravnava pomoću magnetskog senzora. Funkcija alignMotor može se pozvati u bilo kojem trenutku radi poravnavanja gornje ploče. Ova funkcija prvo brzo okreće ploču do 1 punog okreta dok ne vidi podatke magneta iznad praga. Zatim se malo sigurnosno kopira i polako se ponovno pomiče prema naprijed, bilježeći podatke senzora u hodu. Konačno, postavlja položaj na maksimalnu lokaciju podataka o magnetu i vraća pomak položaja na 0. Dakle, maksimalna pozicija magneta uvijek treba biti na (vrh. Pozicija % stepsPerRev)
Boolean sawMagnet; dvostruki magSensorMax = 0; privatna void alignMotor () {
// Pronađite magnet
top. DataInterval = top. MinDataInterval;
sawMagnet = false;
magSensor. SensorChange += magSensorStopMotor; top. VelocityLimit = -1000;
int tryCount = 0;
pokušaj ponovo:
top. TargetPosition += stepsPerRev;
while (top. IsMoving &&! sawMagnet) Thread. Sleep (25);
if (! sawMagnet) {
if (tryCount> 3) {Console. WriteLine ("Poravnanje nije uspjelo"); top. Engaged = false; bottom. Engaged = false; runtest = false; return; }
tryCount ++;
Console. WriteLine ("Jesmo li zaglavljeni? Pokušavamo izraditi sigurnosnu kopiju …"); top. TargetPosition -= 600; while (top. IsMoving) Thread. Sleep (100);
goto tryagain;
}
top. VelocityLimit = -100;
magData = nova lista> (); magSensor. SensorChange += magSensorCollectPositionData; top. TargetPosition += 300; while (top. IsMoving) Thread. Sleep (100);
magSensor. SensorChange -= magSensorCollectPositionData;
top. VelocityLimit = -topVelocityLimit;
KeyValuePair max = magData [0];
foreach (KeyValuePair par u magData) if (pair. Value> max. Value) max = pair;
top. AddPositionOffset (-max. Key);
magSensorMax = max. Value;
top. TargetPosition = 0;
while (top. IsMoving) Thread. Sleep (100);
Console. WriteLine ("Poravnanje uspjelo");
}
Lista> magData;
private void magSensorCollectPositionData (pošiljatelj objekta, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {magData. Add (novi KeyValuePair (top. Position, e. SensorValue)); }
private void magSensorStopMotor (pošiljalac objekta, Phidget22. Events. VoltageRatioInputSensorChangeEventArgs e) {
if (top. IsMoving && e. SensorValue> 5) {top. TargetPosition = top. Position - 300; magSensor. SensorChange -= magSensorStopMotor; sawMagnet = true; }}
Na kraju, donjim motorom se upravlja slanjem u jedan od položaja spremnika perlica. Za ovaj projekt imamo 19 pozicija. Algoritam bira najkraći put i okreće se u smjeru kazaljke na satu ili u suprotnom smjeru.
private int BottomPosition {get {int posn = (int) bottom. Position % stepsPerRev; if (posn <0) posn += stepsPerRev;
return (int) Math. Round ((((posn * beadCompartments) / (double) stepsPerRev));
} }
private void SetBottomPosition (int posn, bool wait = false) {
posn = posn % beadCompartments; dvostruki targetPosn = (posn * stepsPerRev) / beadCompartments;
dvostruka currentPosn = bottom. Position % stepsPerRev;
dvostruki posnDiff = targetPosn - currentPosn;
// Zadržite to kao potpune korake
posnDiff = ((int) (posnDiff / 16)) * 16;
if (posnDiff <= 1600) bottom. TargetPosition += posnDiff; else bottom. TargetPosition - = (stepsPerRev - posnDiff);
ako (čekaj)
while (bottom. IsMoving) Thread. Sleep (50); }
Kamera
OpenCV se koristi za čitanje slika s web kamere. Nit kamere se pokreće prije pokretanja glavne niti za sortiranje. Ova nit kontinuirano čita slike, izračunava prosječnu boju za određenu regiju koristeći Mean i ažurira globalnu varijablu boja. Nit također koristi HoughCircles kako bi pokušao otkriti ili perlicu, ili rupu na gornjoj ploči, kako bi poboljšao područje koje gleda radi otkrivanja boje. Prag i HoughCircles brojevi određeni su pokušajem i greškom i uvelike ovise o web kameri, osvjetljenju i razmaku.
bool runVideo = true; bool videoRunning = false; VideoCapture snimanje; Tema cvThread; Boja otkrivenaBoja; Logičko otkrivanje = netačno; int detectionCnt = 0;
private void cvThreadFunction () {
videoRunning = false;
hvatanje = nova VideoCapture (odabrana kamera);
koristeći (Window window = new Window ("capture")) {
Mat slika = nova Mat (); Mat slika2 = nova Mat (); while (runVideo) {hvatanje. Read (slika); if (image. Empty ()) break;
ako (otkrivanje)
detectCnt ++; else detektujCnt = 0;
if (otkrivanje || circleDetectChecked || showDetectionImgChecked) {
Cv2. CvtColor (image, image2, ColorConversionCodes. BGR2GRAY); Mat Mom = image2. Threshold ((double) Properties. Settings. Default.videoThresh, 255, ThresholdTypes. Binary); ham = ham. GaussianBlur (novi OpenCvSharp. Size (9, 9), 10);
if (showDetectionImgChecked)
slika = mlata;
if (otkrivanje || circleDetectChecked) {
CircleSegment perla = mlatara. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 20, 200, 100, 20, 65); if (bead. Length> = 1) {image. Circle (perle [0]. Center, 3, novi skalarni (0, 100, 0), -1); image. Circle (perle [0]. Center, (int) perle [0]. Radius, novi skalar (0, 0, 255), 3); if (perla [0]. Radius> = 55) {Properties. Settings. Default.x = (decimalna) perla [0]. Center. X + (decimalna) (kuglica [0]. Radius / 2); Properties. Settings. Default.y = (decimalna) perla [0]. Center. Y - (decimalna) (kuglica [0]. Radius / 2); } else {Properties. Settings. Default.x = (decimalna) perla [0]. Center. X + (decimalna) (kuglica [0]. Radius); Properties. Settings. Default.y = (decimalna) perla [0]. Center. Y - (decimalna) (kuglica [0]. Radius); } Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; } else {
CircleSegment krugovi = ham. HoughCircles (HoughMethods. Gradient, 2, /*thres. Rows/4*/ 5, 200, 100, 60, 180);
if (krugovi. Dužina> 1) {Lista xs = krugovi. Odaberite (c => c. Center. X). ToList (); xs. Sort (); Lista ys = krugovi. Odaberite (c => c. Center. Y). ToList (); ys. Sort ();
int medijaanX = (int) xs [xs. Count / 2];
int medijanY = (int) ys [ys. Count / 2];
if (medianX> slika. Širina - 15)
medianX = slika. Širina - 15; if (medianY> image. Height - 15) mediaanY = image. Height - 15;
image. Circle (medijanX, medijanY, 100, novi skalar (0, 0, 150), 3);
if (otkrivanje) {
Properties. Settings. Default.x = medianX - 7; Properties. Settings. Default.y = medijanY - 7; Properties. Settings. Default.size = 15; Properties. Settings. Default.height = 15; }}}}}
Rect r = new Rect ((int) Properties. Settings. Default.x, (int) Properties. Settings. Default.y, (int) Properties. Settings. Default.size, (int) Properties. Settings. Default.height);
Mat beadSample = new Mat (slika, r);
Skalarni avgColor = Cv2. Mean (beadSample); foundColor = Color. FromArgb ((int) avgColor [2], (int) avgColor [1], (int) avgColor [0]);
image. Rectangle (r, novi skalar (0, 150, 0));
window. ShowImage (slika);
Cv2. WaitKey (1); videoRunning = true; }
videoRunning = false;
} }
private void cameraStartBtn_Click (pošiljalac objekta, EventArgs e) {
if (cameraStartBtn. Text == "start") {
cvThread = nova nit (novi ThreadStart (cvThreadFunction)); runVideo = true; cvThread. Start (); cameraStartBtn. Text = "stop"; while (! videoRunning) Thread. Sleep (100);
updateColorTimer. Start ();
} else {
runVideo = false; cvThread. Join (); cameraStartBtn. Text = "start"; }}
Boja
Sada smo u mogućnosti odrediti boju zrna i na osnovu te boje odlučiti u koji spremnik ga staviti.
Ovaj korak se oslanja na poređenje boja. Želimo biti u mogućnosti razlikovati boje kako bismo ograničili lažno pozitivne rezultate, ali i dopustiti dovoljan prag za ograničavanje lažnih negativa. Poređenje boja je zapravo iznenađujuće složeno, jer način na koji računari skladište boje kao RGB i način na koji ljudi percipiraju boje nemaju linearnu korelaciju. Da stvar bude gora, mora se uzeti u obzir i boja svjetla pod kojom se posmatra boja.
Postoje složeni algoritmi za izračunavanje razlike u boji. Koristimo CIE2000, koji daje broj blizu 1 ako se 2 boje ne razlikuju od čovjeka. Za ove komplikovane proračune koristimo biblioteku ColorMine C#. Utvrđeno je da vrijednost DeltaE od 5 nudi dobar kompromis između lažno pozitivnog i lažno negativnog.
Budući da često postoji više boja nego spremnika, posljednja pozicija je rezervirana kao spremna kanta. Općenito sam ovo ostavio po strani da bih prošao kroz mašinu na drugom prolazu.
Lista
colours = new List (); List colorPanels = new List (); Boje listeTxts = nova Lista (); List colorCnts = novi List ();
const int numColorSpots = 18;
const int unknownColorIndex = 18; int findColorPosition (Boja c) {
Console. WriteLine ("Traženje boje …");
var cRGB = novi Rgb ();
cRGB. R = c. R; cRGB. G = c. G; cRGB. B = c. B;
int bestMatch = -1;
dvostruko podudaranjeDelta = 100;
for (int i = 0; i <boje. Count; i ++) {
var RGB = novi Rgb ();
RGB. R = boje . R; RGB. G = boje . G; RGB. B = boje . B;
double delta = cRGB. Compare (RGB, novi CieDe2000Comparison ());
// dvostruka delta = deltaE (c, boje ); Console. WriteLine ("DeltaE (" + i. ToString () + "):" + delta. ToString ()); if (delta <matchDelta) {matchDelta = delta; bestMatch = i; }}
if (matchDelta <5) {Console. WriteLine ("Pronađeno! (Posn:" + bestMatch + "Delta:" + matchDelta + ")"); return bestMatch; }
if (colors. Count <numColorSpots) {Console. WriteLine ("Nova boja!"); boje. Dodaj (c); this. BeginInvoke (nova akcija (setBackColor), novi objekt {colours. Count - 1}); writeOutColors (); return (boje. Broj - 1); } else {Console. WriteLine ("Nepoznata boja!"); return unknownColorIndex; }}
Logika sortiranja
Funkcija sortiranja objedinjuje sve dijelove kako bi zapravo sortirali perlice. Ova funkcija radi u namjenskoj niti; pomicanje gornje ploče, otkrivanje boje perli, stavljanje u kantu, paženje da gornja ploča ostane poravnata, brojanje perlica itd. Također prestaje s radom kada se kanta za hvatanje napuni - U protivnom jednostavno završimo s prelivanjem perlica.
Boolean runtest = false; void colourTest () {
if (! top.zaposlen)
top. Engaged = true;
if (! dno. Angažirano)
bottom. Engaged = true;
while (runtest) {
nextMagnet (istina);
Thread. Sleep (100); probajte {if (magSensor. SensorValue <(magSensorMax - 4)) alignMotor (); } uhvati {alignMotor (); }
nextCamera (true);
otkrivanje = tačno;
while (detectionCnt <5) Thread. Sleep (25); Console. WriteLine ("Detect Count:" + detectionCnt); otkrivanje = lažno;
Boja c = otkrivenaBoja;
this. BeginInvoke (nova akcija (setColorDet), novi objekt {c}); int i = findColorPosition (c);
SetBottomPosition (i, tačno);
nextHole (true); colorCnts ++; this. BeginInvoke (nova akcija (setColorTxt), novi objekt {i}); Thread. Sleep (250);
if (colorCnts [unknownColorIndex]> 500) {
top. Engaged = false; bottom. Engaged = false; runtest = false; this. BeginInvoke (nova akcija (setGoGreen), null); return; }}}
private void colourTestBtn_Click (pošiljalac objekta, EventArgs e) {
if (colourTestThread == null ||! colourTestThread. IsAlive) {colourTestThread = nova nit (novi ThreadStart (colourTest)); runtest = true; colourTestThread. Start (); colourTestBtn. Text = "STOP"; colourTestBtn. BackColor = Color. Red; } else {runtest = false; colourTestBtn. Text = "IDI"; colourTestBtn. BackColor = Color. Green; }}
U ovom trenutku imamo radni program. Neki dijelovi koda izostavljeni su iz članka, pa pogledajte izvor da biste ga zapravo pokrenuli.
Druga nagrada na takmičenju iz optike
Preporučuje se:
Robot za sortiranje recikliranja: 15 koraka (sa slikama)
Robot za sortiranje recikliranja: Jeste li znali da se prosječna stopa zagađenja u zajednicama i preduzećima kreće do 25%? To znači da se svaki četvrti komad recikliranja koji bacite ne reciklira. To je uzrokovano ljudskom greškom u centrima za reciklažu. Traditi
Deda Mrazov šešir za sortiranje: 10 koraka (sa slikama)
Santa's Sorting Hat: Blisko smo surađivali s Djedovom radionicom kako bismo vam predstavili ovu inovaciju u nestašnoj ili lijepoj komunikaciji s spiskom. Sada možete provjeriti u stvarnom vremenu jesu li vaša dobra i loša djela uticala na vaš položaj na Deda Mrazovoj listi Naughty ili Nice! Zabavan projekt
Mašina za sortiranje čarobnog mramora LittleBits: 11 koraka (sa slikama)
Mašina za sortiranje čarobnog mramora LittleBits: Jeste li ikada htjeli sortirati mramore? Onda ste mogli napraviti ovu mašinu. Više nikada nećete morati prelistavati vreću mramora! To je čarobna mašina za sortiranje mramora, koja koristi senzor boje za Adafruit, tip TCS34725 i Leonarda Arduina iz
Mašina za sortiranje vijaka: 7 koraka (sa slikama)
Mašina za sortiranje vijaka: Jednog dana u laboratoriji (FabLab Moskva), vidio sam svog kolegu zauzetog sortiranjem pune kutije vijaka, matica, prstenova i drugog hardvera. Zaustavivši se pored njega, gledao sam na trenutak i rekao: " To bi bio savršen posao za mašinu. &Quot; Nakon kratkog pogleda
Robotsko srce - možete napraviti proizvod!: 7 koraka (sa slikama)
Robotsko srce - možete napraviti proizvod !: Kada kupujete elektroniku, rijetko dolazi do golih PCB -a. Iz različitih razloga, PCB se nalazi u kućištu. Tako da ću u ovom uputstvu pokazati kako možete uzeti ideju i pretvoriti je u proizvod (ish)! SMD lemljenje može izgledati zastrašujuće, ali obećavam vam