บทช่วยสอน OpenCV: การตรวจจับวัตถุแบบเรียลไทม์โดยใช้ MSER ใน iOS
เผยแพร่แล้ว: 2022-03-11ในช่วงไม่กี่ปีที่ผ่านมา ประสิทธิภาพของโทรศัพท์มือถือโดยเฉลี่ยเพิ่มขึ้นอย่างมาก ไม่ว่าจะเป็นแรงม้าของ CPU หรือความจุของ RAM ก็ตาม ตอนนี้การทำงานที่ต้องใช้การคำนวณอย่างหนักบนฮาร์ดแวร์มือถือทำได้ง่ายขึ้น แม้ว่าเทคโนโลยีมือถือเหล่านี้จะมุ่งไปในทิศทางที่ถูกต้อง แต่ก็ยังมีอีกมากที่ต้องทำบนแพลตฟอร์มมือถือ โดยเฉพาะอย่างยิ่งกับการถือกำเนิดของความเป็นจริงเสริมความเป็นจริงเสมือนและปัญญาประดิษฐ์
ความท้าทายหลักในการมองเห็นด้วยคอมพิวเตอร์คือการตรวจจับวัตถุที่สนใจในภาพ ดวงตาและสมองของมนุษย์ทำงานได้อย่างยอดเยี่ยม และการเลียนแบบสิ่งนี้ในเครื่องจักรยังคงเป็นความฝัน ในช่วงหลายทศวรรษที่ผ่านมา มีการพัฒนาแนวทางเพื่อเลียนแบบสิ่งนี้ในเครื่องจักร และกำลังดีขึ้นเรื่อยๆ
ในบทช่วยสอนนี้ เราจะสำรวจอัลกอริทึมที่ใช้ในการตรวจจับหยดในรูปภาพ นอกจากนี้ เราจะใช้อัลกอริธึมจาก OpenCV ไลบรารีโอเพนซอร์ส เพื่อใช้แอปพลิเคชัน iPhone ต้นแบบที่ใช้กล้องด้านหลังเพื่อรับภาพและตรวจจับวัตถุในนั้น
กวดวิชา OpenCV
OpenCV เป็นไลบรารีโอเพ่นซอร์สที่ให้การใช้งานคอมพิวเตอร์วิทัศน์หลักและอัลกอริธึมการเรียนรู้ของเครื่อง หากคุณต้องการใช้แอพพลิเคชั่นเพื่อตรวจจับใบหน้า เล่นไพ่บนโต๊ะโป๊กเกอร์ หรือแม้แต่แอพพลิเคชั่นง่ายๆ สำหรับใส่เอฟเฟกต์ลงบนภาพโดยพลการ OpenCV เป็นตัวเลือกที่ดี
OpenCV เขียนด้วย C/C++ และมีไลบรารีตัวห่อหุ้มสำหรับแพลตฟอร์มหลักทั้งหมด ทำให้ใช้งานได้ง่ายเป็นพิเศษในสภาพแวดล้อม iOS หากต้องการใช้ภายในแอปพลิเคชัน Objective-C iOS ให้ดาวน์โหลด OpenCV iOS Framework จากเว็บไซต์ทางการ โปรดตรวจสอบให้แน่ใจว่าคุณใช้ OpenCV เวอร์ชัน 2.4.11 สำหรับ iOS (ซึ่งบทความนี้ถือว่าคุณกำลังใช้อยู่) เนื่องจากเวอร์ชันล่าสุด 3.0 มีการเปลี่ยนแปลงบางอย่างในการทำงานร่วมกันในการจัดระเบียบไฟล์ส่วนหัว ข้อมูลโดยละเอียดเกี่ยวกับวิธีการติดตั้งได้รับการบันทึกไว้บนเว็บไซต์
MSER
MSER ย่อมาจาก Maximally Stable Extremal Regions เป็นหนึ่งในวิธีการที่หลากหลายสำหรับการตรวจจับ Blob ภายในรูปภาพ พูดง่ายๆ ก็คือ อัลกอริธึมระบุชุดพิกเซลที่ต่อเนื่องกันซึ่งมีความเข้มของพิกเซลที่ขอบด้านนอกสูงกว่า (ตามเกณฑ์ที่กำหนด) มากกว่าความเข้มของพิกเซลในขอบเขตภายใน กล่าวกันว่าภูมิภาคดังกล่าวจะมีเสถียรภาพสูงสุดหากไม่เปลี่ยนแปลงไปมากตามระดับความเข้มข้นที่ต่างกัน
แม้ว่าจะมีอัลกอริธึมการตรวจจับ Blob อื่นๆ อยู่จำนวนหนึ่ง แต่ MSER ก็ถูกเลือกที่นี่เนื่องจากมีความซับซ้อนในการทำงานตามเวลาที่ค่อนข้างน้อยของ O(n log(log(n))) โดยที่ n คือจำนวนพิกเซลทั้งหมดบนรูปภาพ อัลกอริธึมยังทนทานต่อการเบลอและปรับขนาด ซึ่งเป็นประโยชน์ในการประมวลผลภาพที่ได้มาจากแหล่งข้อมูลแบบเรียลไทม์ เช่น กล้องของโทรศัพท์มือถือ
สำหรับจุดประสงค์ของบทช่วยสอนนี้ เราจะออกแบบแอปพลิเคชันเพื่อตรวจจับโลโก้ของ Toptal สัญลักษณ์นี้มีมุมที่แหลมคม และนั่นอาจทำให้คนคิดว่าอัลกอริธึมการตรวจจับมุมอาจมีประสิทธิภาพในการตรวจจับโลโก้ของ Toptal ท้ายที่สุดแล้ว อัลกอริธึมดังกล่าวทั้งใช้งานง่ายและเข้าใจ แม้ว่าวิธีการแบบอิงตามมุมอาจมีอัตราความสำเร็จสูงในการตรวจจับวัตถุที่แยกจากพื้นหลังอย่างชัดเจน (เช่น วัตถุสีดำบนพื้นหลังสีขาว) การตรวจจับโลโก้ของ Toptal แบบเรียลไทม์ในโลกแห่งความเป็นจริงอาจเป็นเรื่องยาก ภาพที่อัลกอริทึมจะตรวจจับมุมหลายร้อยมุมอย่างต่อเนื่อง
กลยุทธ์
สำหรับแต่ละเฟรมของภาพที่แอปพลิเคชันได้รับผ่านกล้อง จะถูกแปลงเป็นระดับสีเทาก่อน รูปภาพระดับสีเทามีเพียงช่องสีเดียว แต่โลโก้จะมองเห็นได้ วิธีนี้ทำให้อัลกอริธึมจัดการกับภาพได้ง่ายขึ้น และลดปริมาณข้อมูลที่อัลกอริทึมต้องดำเนินการอย่างมากเพื่อให้ได้กำไรเพียงเล็กน้อยหรือไม่มีเลย
ต่อไป เราจะใช้อัลกอริทึมการนำ OpenCV ไปใช้งานเพื่อแยก MSERs ทั้งหมด ถัดไป MSER แต่ละตัวจะถูกทำให้เป็นมาตรฐานโดยเปลี่ยนรูปสี่เหลี่ยมที่มีขอบต่ำสุดเป็นสี่เหลี่ยมจัตุรัส ขั้นตอนนี้มีความสำคัญเนื่องจากโลโก้อาจมาจากมุมและระยะทางที่ต่างกัน ซึ่งจะช่วยเพิ่มความทนทานต่อการบิดเบือนของเปอร์สเปคทีฟ
นอกจากนี้ยังมีการคำนวณคุณสมบัติจำนวนหนึ่งสำหรับ MSER แต่ละตัว:
- จำนวนหลุม
- อัตราส่วนของพื้นที่ MSER ต่อพื้นที่ของลำตัวนูน
- อัตราส่วนของพื้นที่ MSER ต่อพื้นที่ของสี่เหลี่ยมพื้นที่ต่ำสุด
- อัตราส่วนความยาวของโครงกระดูก MSER ต่อพื้นที่ของ MSER
- อัตราส่วนของพื้นที่ MSER ต่อพื้นที่ของรูปร่างที่ใหญ่ที่สุด
เพื่อตรวจจับโลโก้ของ Toptal ในภาพ คุณสมบัติของ MSER ทั้งหมดจะถูกเปรียบเทียบกับคุณสมบัติโลโก้ Toptal ที่เรียนรู้ไปแล้ว สำหรับจุดประสงค์ของบทช่วยสอนนี้ ความแตกต่างสูงสุดที่อนุญาตสำหรับแต่ละพร็อพเพอร์ตี้ถูกเลือกโดยสังเกตจากประสบการณ์
ในที่สุด ภูมิภาคที่คล้ายกันมากที่สุดจะถูกเลือกเป็นผล
แอปพลิเคชัน iOS
การใช้ OpenCV จาก iOS เป็นเรื่องง่าย หากคุณยังไม่ได้ดำเนินการ ต่อไปนี้คือโครงร่างโดยย่อของขั้นตอนที่เกี่ยวข้องในการตั้งค่า Xcode เพื่อสร้างแอปพลิเคชัน iOS และใช้ OpenCV ในนั้น:
สร้างชื่อโครงการใหม่ “SuperCool Logo Detector” เป็นภาษา ให้เลือก Objective-C
เพิ่มไฟล์ Prefix Header (.pch) ใหม่และตั้งชื่อว่า PrefixHeader.pch
ไปที่โปรเจ็กต์ “SuperCool Logo Detector” บิลด์เป้าหมาย และในแท็บการตั้งค่าบิลด์ ให้ค้นหาการตั้งค่า “Prefix Headers” คุณสามารถค้นหาได้ในส่วนภาษา LLVM หรือใช้คุณลักษณะการค้นหา
เพิ่ม “PrefixHeader.pch” ในการตั้งค่า Prefix Headers
ณ จุดนี้ หากคุณยังไม่ได้ติดตั้ง OpenCV สำหรับ iOS 2.4.11 ให้ทำทันที
ลากและวางเฟรมเวิร์กที่ดาวน์โหลดมาลงในโปรเจ็กต์ ทำเครื่องหมายที่ "Linked Frameworks and Libraries" ในการตั้งค่าเป้าหมายของคุณ (ควรเพิ่มให้อัตโนมัติแต่จะดีกว่าเพื่อความปลอดภัย)
นอกจากนี้ เชื่อมโยงเฟรมเวิร์กต่อไปนี้:
- AVFoundation
- ห้องสมุดทรัพย์สิน
- CoreMedia
เปิด "PrefixHeader.pch" และเพิ่ม 3 บรรทัดต่อไปนี้:
#ifdef __cplusplus #include <opencv2/opencv.hpp> #endif”
เปลี่ยนนามสกุลของไฟล์โค้ดที่สร้างโดยอัตโนมัติจาก “ .m” เป็น “ .mm” OpenCV เขียนด้วย C ++ และด้วย *.mm คุณกำลังบอกว่าคุณจะใช้ Objective-C++
นำเข้า “opencv2/highgui/cap_ios.h” ใน ViewController.h และเปลี่ยน ViewController เพื่อให้สอดคล้องกับโปรโตคอล CvVideoCameraDelegate:
#import <opencv2/highgui/cap_ios.h>
เปิด Main.storyboard และวาง UIImageView บนตัวควบคุมมุมมองเริ่มต้น
สร้างทางออกให้กับ ViewController.mm ชื่อ “imageView”
สร้างตัวแปร “CvVideoCamera *camera” ใน ViewController.h หรือ ViewController.mm และเริ่มต้นโดยอ้างอิงจากกล้องด้านหลัง:
camera = [[CvVideoCamera alloc] initWithParentView: _imageView]; camera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack; camera.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480; camera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait; camera.defaultFPS = 30; camera.grayscaleMode = NO; camera.delegate = self;
หากคุณสร้างโปรเจ็กต์ตอนนี้ Xcode จะเตือนคุณว่าคุณไม่ได้ใช้เมธอด “processImage” จาก CvVideoCameraDelegate สำหรับตอนนี้ และเพื่อความเรียบง่าย เราจะเพียงแค่รับภาพจากกล้องและซ้อนทับด้วยข้อความธรรมดา:
- เพิ่มบรรทัดเดียวใน “viewDidAppear”:
[camera start];
ตอนนี้ หากคุณเรียกใช้แอปพลิเคชัน แอปพลิเคชันจะขออนุญาตจากคุณในการเข้าถึงกล้อง จากนั้นคุณควรเห็นวิดีโอจากกล้อง
ในวิธี “processImage” ให้เพิ่มสองบรรทัดต่อไปนี้:
const char* str = [@"Toptal" cStringUsingEncoding: NSUTF8StringEncoding]; cv::putText(image, str, cv::Point(100, 100), CV_FONT_HERSHEY_PLAIN, 2.0, cv::Scalar(0, 0, 255));
ที่มันสวยมาก ตอนนี้คุณมีแอปพลิเคชั่นที่ง่ายมากที่วาดข้อความ “Toptal” บนรูปภาพจากกล้อง ตอนนี้เราสามารถสร้างแอปพลิเคชันตรวจจับโลโก้เป้าหมายจากแอปที่ง่ายกว่านี้ได้แล้ว เพื่อความกระชับ ในบทความนี้เราจะพูดถึงส่วนโค้ดเพียงไม่กี่ส่วนเท่านั้นที่มีความสำคัญต่อการทำความเข้าใจวิธีการทำงานของแอปพลิเคชันโดยรวม รหัสบน GitHub มีความคิดเห็นจำนวนมากพอสมควรเพื่ออธิบายว่าแต่ละส่วนทำอะไร
เนื่องจากแอปพลิเคชันมีจุดประสงค์เดียวเท่านั้น ในการตรวจจับโลโก้ของ Toptal ทันทีที่เปิดตัว คุณลักษณะ MSER จะถูกดึงออกจากภาพเทมเพลตที่กำหนดและค่าจะถูกเก็บไว้ในหน่วยความจำ:
cv::Mat logo = [ImageUtils cvMatFromUIImage: templateImage]; //get gray image cv::Mat gray; cvtColor(logo, gray, CV_BGRA2GRAY); //mser with maximum area is std::vector<cv::Point> maxMser = [ImageUtils maxMser: &gray]; //get 4 vertices of the maxMSER minrect cv::RotatedRect rect = cv::minAreaRect(maxMser); cv::Point2f points[4]; rect.points(points); //normalize image cv::Mat M = [GeometryUtil getPerspectiveMatrix: points toSize: rect.size]; cv::Mat normalizedImage = [GeometryUtil normalizeImage: &gray withTranformationMatrix: &M withSize: rect.size.width]; //get maxMser from normalized image std::vector<cv::Point> normalizedMser = [ImageUtils maxMser: &normalizedImage]; //remember the template self.logoTemplate = [[MSERManager sharedInstance] extractFeature: &normalizedMser]; //store the feature [self storeTemplate];
แอปพลิเคชันมีหน้าจอเดียวที่มีปุ่มเริ่ม/หยุด และข้อมูลที่จำเป็นทั้งหมด เช่น FPS และจำนวน MSER ที่ตรวจพบ จะถูกวาดโดยอัตโนมัติบนรูปภาพ ตราบใดที่ยังไม่หยุดแอปพลิเคชัน ทุกเฟรมของภาพในกล้องจะเรียกใช้เมธอด processImage ต่อไปนี้:
-(void)processImage:(cv::Mat &)image { cv::Mat gray; cvtColor(image, gray, CV_BGRA2GRAY); std::vector<std::vector<cv::Point>> msers; [[MSERManager sharedInstance] detectRegions: gray intoVector: msers]; if (msers.size() == 0) { return; }; std::vector<cv::Point> *bestMser = nil; double bestPoint = 10.0; std::for_each(msers.begin(), msers.end(), [&] (std::vector<cv::Point> &mser) { MSERFeature *feature = [[MSERManager sharedInstance] extractFeature: &mser]; if(feature != nil) { if([[MLManager sharedInstance] isToptalLogo: feature] ) { double tmp = [[MLManager sharedInstance] distance: feature ]; if ( bestPoint > tmp ) { bestPoint = tmp; bestMser = &mser; } } } }); if (bestMser) { NSLog(@"minDist: %f", bestPoint); cv::Rect bound = cv::boundingRect(*bestMser); cv::rectangle(image, bound, GREEN, 3); } else { cv::rectangle(image, cv::Rect(0, 0, W, H), RED, 3); } // Omitted debug code [FPS draw: image]; }
โดยพื้นฐานแล้ววิธีนี้จะสร้างสำเนาระดับสีเทาของภาพต้นฉบับ จะระบุ MSER ทั้งหมดและแยกคุณลักษณะที่เกี่ยวข้อง ให้คะแนน MSER แต่ละตัวสำหรับความคล้ายคลึงกันกับเทมเพลต และเลือกสิ่งที่ดีที่สุด สุดท้าย จะวาดขอบเขตสีเขียวรอบๆ MSER ที่ดีที่สุด และซ้อนทับรูปภาพด้วยข้อมูลเมตา
ด้านล่างนี้คือคำจำกัดความของคลาสสำคัญสองสามคลาสและวิธีการของคลาสเหล่านี้ในแอปพลิเคชันนี้ วัตถุประสงค์ของพวกเขาอธิบายไว้ในความคิดเห็น
GeometryUtil.h
/* This static class provides perspective transformation function */ @interface GeometryUtil : NSObject /* Return perspective transformation matrix for given points to square with origin [0,0] and with size (size.width, size.width) */ + (cv::Mat) getPerspectiveMatrix: (cv::Point2f[]) points toSize: (cv::Size2f) size; /* Returns new perspecivly transformed image with given size */ + (cv::Mat) normalizeImage: (cv::Mat *) image withTranformationMatrix: (cv::Mat *) M withSize: (float) size; @end
MSERManager.h
/* Singelton class providing function related to msers */ @interface MSERManager : NSObject + (MSERManager *) sharedInstance; /* Extracts all msers into provided vector */ - (void) detectRegions: (cv::Mat &) gray intoVector: (std::vector<std::vector<cv::Point>> &) vector; /* Extracts feature from the mser. For some MSERs feature can be NULL !!! */ - (MSERFeature *) extractFeature: (std::vector<cv::Point> *) mser; @end
MLManager.h
/* This singleton class wraps object recognition function */ @interface MLManager : NSObject + (MLManager *) sharedInstance; /* Stores feature from the biggest MSER in the templateImage */ - (void) learn: (UIImage *) templateImage; /* Sum of the differences between logo feature and given feature */ - (double) distance: (MSERFeature *) feature; /* Returns true if the given feature is similar to the one learned from the template */ - (BOOL) isToptalLogo: (MSERFeature *) feature; @end
หลังจากที่ทุกอย่างเชื่อมต่อเข้าด้วยกันแล้ว ด้วยแอปพลิเคชันนี้ คุณควรจะสามารถใช้กล้องของอุปกรณ์ iOS ของคุณเพื่อตรวจจับโลโก้ของ Toptal จากมุมและทิศทางที่ต่างกันได้
บทสรุป
ในบทความนี้ เราได้แสดงให้เห็นว่าการตรวจหาวัตถุอย่างง่ายจากรูปภาพโดยใช้ OpenCV นั้นง่ายเพียงใด รหัสทั้งหมดมีอยู่ใน GitHub รู้สึกอิสระที่จะแยกและส่งคำขอแบบพุชเนื่องจากยินดีให้ความช่วยเหลือ
ตามความเป็นจริงสำหรับปัญหาการเรียนรู้ของเครื่อง อัตราความสำเร็จของการตรวจจับโลโก้ในแอปพลิเคชันนี้อาจเพิ่มขึ้นโดยใช้ชุดคุณลักษณะต่างๆ และวิธีการที่แตกต่างกันสำหรับการจัดประเภทวัตถุ อย่างไรก็ตาม ฉันหวังว่าบทความนี้จะช่วยคุณในการเริ่มต้นการตรวจจับวัตถุโดยใช้ MSER และการประยุกต์ใช้เทคนิคการมองเห็นด้วยคอมพิวเตอร์โดยทั่วไป
อ่านเพิ่มเติม
- J. Matas, O. Chum, M. Urban และ T. Pajdla “สเตอริโอเบสไลน์กว้างที่ทนทานจากบริเวณปลายสุดที่เสถียรสูงสุด”
- นอยมันน์, ลูคัส; มาทัส, จิริ (2554). “วิธีการโลคัลไลเซชันข้อความและการจดจำในรูปภาพจริง”