Тэгээд сүүлийн хэдэн сар роботоо хөл бөмбөг тоглодог болгох гэж баахан үйлээ үзэв. Та хэдэд бас иймэрхүү зүйл сонирхолтой байх болов уу гээд энэ тухай бичиж байхаар шийдлээ.
Роботоо хөл бөмбөг тоглуулъя гэвэл хамгийн түрүүнд хийх ёстой зүйл бол камераас ирж байгаа зурагнаас хөл бөмбөгийн талбай, бөмбөг, хаалга болон бусад роботууд хаана байгааг олох явдал юм. Үүнийг хийх хамгийн амархан бөгөөд өргөн дэлгэрсэн арга бол өнгөөр хайх. Хөл бөмбөгийн талбай ногоон өнгөтэй, бөмбөг улбар шар өнгөтэй, хаалга нь цэнхэр юм уу шар өнгөтэй тул энэ өнгүүдтэй төстэй өнгүүдийг зурагнаас ялгаж аваад тэндээсээ хайсан объектуудаа олно.
Ингэж зурагнаас ямар нэгэн объект хайх, таних талаар судалдаг мэдээлэл технологийн салбарыг англиар Computer Vision гэж нэрлэнэ. Үүнд зориулсан янз бүрийн бэлэн library байдгаас хамгийн өргөн тархсан нь OpenCV (Open Computer Vision). Та үүнийг ашиглаад C, Python болон Java дээр computer vision программ бичиж болно.
Энэ бичлэгтээ хэрхэн OpenCV ашиглан Python дээр бөмбөг таних талаар бичье. Доорх зурагнаас бөмбөг хайх болно:
Эхлээд OpenCV импортлоод дараа нь зургийн файлаа зургийн объект болгох хэрэгтэй:
import cv2.cv as cv | |
image = cv.LoadImage('test.jpg') |
Ямар нэгэн өнгөтэй төстэй өнгө хайхад та бидний мэддэг RGB color space сайн тохирдоггүй тул HSV юм уу YCrCb гэх мэт color space ашиглах хэрэгтэй болдог. HSV ашиглавал хүний арьсны өнгө хайхад амар гэдэг бөгөөд бусад тохиолдолд нь YCrCb ашиглавал зүгээр гэдэг тул бөмбөг хайхдаа YCrCb өнгө ашиглая. Улаан, ногоон, цэнхэр өнгө ашиглаж RGB color space бусад бүх өнгийг үүсгэдэг бол YCrCb color space маань хар/цагаан (Y), цэнхэр/шар (Cb), улаан/ногоон (Cr) 3 өнгөний компонент ашиглаж бусад бүх өнгийг үүсгэдэг аж. Аналог телевизийн сигнал бас яг иймэрхүү color space ашигладаг бөгөөд хар цагаан телевиз бол зөвхөн Y компонент үзүүлээд харин өнгөт телевиз бол бүгдийг нь үзүүлдэг юм байна. Одоо зургаа RGB-аас (OpenCV яг RGB ашигладаггүй харин BGR ашиглана, цэнхэр болон улаан өнгө хадгалах байрлал урвуу) YCrCb-руу хөрвүүлье:
ycrcbImage = cv.CreateImage(cv.GetSize(image), cv.IPL_DEPTH_8U, 3) | |
cv.CvtColor(image, ycrcbImage, cv.CV_BGR2YCrCb) |
Хайх гэж байгаа өнгө yColor, crColor, cvColor гэсэн ямар нэгэн утгатай байг. Тэгвэл энэ өнгөтэй төстэй өнгөнүүд энэ 3 утгатай ойролцоо байх бөгөөд хир ойр хол байхыг yThreshold, crThreshold болон cbTreshold утга заадаг байг. Тэгвэл доорх аргаар бүх төстэй өнгөтэй цэгүүдийг ялгаж авч болно:
blobImage = cv.CreateImage(cv.GetSize(image), cv.IPL_DEPTH_8U, 1) | |
cv.InRangeS(ycrcbImage, cv.Scalar(yColor - yThreshold, crColor - crThreshold, cbColor - cbThreshold, 0), cv.Scalar(yColor + yThreshold, crColor + crThreshold, cbColor + cbThreshold, 0), blobImage) |
Жишээ болгож хөл бөмбөгийн талбайн ногоон өнгөтэй бүх цэгүүдийг яалгаж авч нэг цонх дээр үзүүлье:
cv.NamedWindow('Result') | |
cv.ShowImage('Result', blobImage) |
Иймэрхүү харагдаж байна:
Талбай, бөмбөг, хаалга гурвыг хайгаад өнгө оруулбал:
Ингэхэд yColor, crColor, cbColor, yThreshold, crThreshold болон cbThreshold -ын утгуудыг хэрхэн тохируулах вэ? Жишээ нь бөмбөг улбар шар өнгөтэй гэж цаанаас нь зааж өгсөн боловч тэмцээн зохион байгуулж байгаа тал яг ямар өнгөтэй бөмбөг ашигласан, тэмцээн болох өрөөний гэрэлтүүлэг ямар байгаа (хурц гэрэл бөмбөг дээр гялбахад улбар шар өнгө шал өөр өнгө болж харагдана), камерын линз болон sensor ямар нэгэн асуудалтай байх гэх мэт өчнөөн шалтгаанаас болоод бөмбөг яг ийм өнгөтэй байж ёстой гэж шууд утга оноож болохгүй. Тийм учир тухайн нөхцөл байдлаас хамаараад тэмцээн эхлэхийн өмнө камераас ирж байгаа зураг дээр энэ бөмбөг энэ талбай гэх мэтээр хулганаар зааж өгч yColor, crColor болон cbColor хувьсагчуудад утга онооно. Дараа нь yThreshold, crThreshold болон cbThreshold -уудыг нэмж хасаж объект хир таньж байгаагаас хамааруулж тохируулна.
Төстэй өнгөтэй цэгүүдийг олсны дараа тэр цэгүүдээ групп болгож яг хайсан объект мөн эсэхийг шалгах хэрэгтэй. Жишээ нь бөмбөг бөөрөнхий байх ёстой (арай амархан шалгаж болох нөхцөл: бөмбөг квадрат дотор багтах ёстой) харин хаалга бол босоо хоёр дөрвөлжин хэвтээ нэг дөрвөлжнөөс бүтнэ. Доорх код олдсон цэгүүд дотор 10x10 аас том хэмжээтэй бөгөөд квадрат дотор багтах цул байгаа эсэхийг шалгана:
contour = cv.FindContours(blobImage, cv.CreateMemStorage()) | |
while contour != None: | |
if cv.ContourArea(contour) > 10*10: | |
rect = cv.BoundingRect(contour) | |
if abs(rect[2] - rect[3]) < 4: | |
# бөмбөг олдов | |
contour = contour.h_next() |
Бөмбөг олсныг тойруулж дөрвөлжин зурсан байдал:
Ингээд хэдхэн мөр код ашиглаад зурагнаас бөмбөг олох боломжтой аж. Гэхдээ жинхэнэ амьдрал дээр мэдээж ийм хялбар биш л дээ. Робот зурагнаас объект танихаас гадна өөрийгөө талбай дээр хаана байгаад мэдэх (localisation), зураг дээр дүрслэгдээгүй роботууд болон бөмбөг хаана байгааг мэдэх (tracking), унахгүй явах (motion control) гэх мэт өчнөөн зүйл хийдэг тул Computer Vision-д нэг их CPU time оногддоггүй. Ийм учир нэлээд өөрөөр (мэдээж хэцүүгээр) хийх хэрэгтэй болдог.
Дээрх зургуудыг үүсгэхэд ашигласан эх кодыг доор хавсаргав. Зураг дээр хулганаар дарж хайх өнгөө тохируулна, threshold утгуудыг trackbar-аар тохируулна
import cv2.cv as cv | |
class SimpleBallRecognition: | |
def __init__(self, image): | |
self.bgrImage = image | |
self.ycrcbImage = cv.CreateImage(cv.GetSize(self.bgrImage), cv.IPL_DEPTH_8U, 3) | |
self.resultImage = cv.CreateImage(cv.GetSize(self.bgrImage), cv.IPL_DEPTH_8U, 3) | |
self.tmpGrayImage = cv.CreateImage(cv.GetSize(self.bgrImage), cv.IPL_DEPTH_8U, 1) | |
cv.CvtColor(self.bgrImage, self.ycrcbImage, cv.CV_BGR2YCrCb) | |
self.selectedBGRColor = None | |
self.selectedYCrCbColor = None | |
cv.SetMouseCallback('Source', self.onMouse) | |
self.yThreshold = 58; | |
self.crThreshold = 20; | |
self.cbThreshold = 28; | |
cv.CreateTrackbar('Y-Threshold', 'Threshold', self.yThreshold, 255, self.yThresholdChanged); | |
cv.CreateTrackbar('Cr-Threshold', 'Threshold', self.crThreshold, 255, self.crThresholdChanged); | |
cv.CreateTrackbar('Cb-Threshold', 'Threshold', self.cbThreshold, 255, self.cbThresholdChanged); | |
self.findBall() | |
def onMouse(self, event, x, y, flag, param): | |
if event == cv.CV_EVENT_LBUTTONDOWN: | |
print "Mouse Click on (" + str(x) + "," + str(y) + ")" | |
self.selectedBGRColor = cv.Get2D(self.bgrImage, y, x) | |
self.selectedYCrCbColor = cv.Get2D(self.ycrcbImage, y, x) | |
self.findBall() | |
def yThresholdChanged(self, sliderPos): | |
self.yThreshold = sliderPos | |
print "New Y Threshold: " + str(self.yThreshold) | |
self.findBall() | |
def crThresholdChanged(self, sliderPos): | |
self.crThreshold = sliderPos | |
print "New Cr Threshold: " + str(self.crThreshold) | |
self.findBall() | |
def cbThresholdChanged(self, sliderPos): | |
self.cbThreshold = sliderPos | |
print "New Cb Threshold: " + str(self.cbThreshold) | |
self.findBall() | |
def findBall(self): | |
cv.Zero(self.resultImage) | |
cv.Zero(self.tmpGrayImage) | |
if self.selectedYCrCbColor != None: | |
cv.InRangeS(self.ycrcbImage, | |
cv.Scalar(self.selectedYCrCbColor[0] - self.yThreshold, | |
self.selectedYCrCbColor[1] - self.crThreshold, | |
self.selectedYCrCbColor[2] - self.cbThreshold, 0), | |
cv.Scalar(self.selectedYCrCbColor[0] + self.yThreshold, | |
self.selectedYCrCbColor[1] + self.crThreshold, | |
self.selectedYCrCbColor[2] + self.cbThreshold, 0), self.tmpGrayImage) | |
cv.Copy(self.bgrImage, self.resultImage, self.tmpGrayImage) | |
contour = cv.FindContours(self.tmpGrayImage, cv.CreateMemStorage()) | |
while contour != None: | |
if cv.ContourArea(contour) > 10*10: | |
rect = cv.BoundingRect(contour) | |
if abs(rect[2] - rect[3]) < 4: | |
cv.Rectangle(self.resultImage, (rect[0], rect[1]), (rect[0] + rect[2], rect[1] + rect[3]), cv.CV_RGB(255, 0, 0)); | |
print "Ball found in: (" + str(rect[0] + rect[2]/2) + "," + str(rect[1] + rect[3]/2) + ")" | |
contour = contour.h_next() | |
cv.ShowImage('Result', self.resultImage) | |
if __name__ == '__main__': | |
image = cv.LoadImage('test.jpg') | |
cv.NamedWindow('Threshold') | |
cv.NamedWindow('Source') | |
cv.NamedWindow('Result') | |
cv.ShowImage('Source', image) | |
SimpleBallRecognition(image) | |
cv.WaitKey(0) | |
cv.DestroyAllWindows() |