การตรวจจับวัตถุโดยใช้ OpenCV และ Swift

เผยแพร่แล้ว: 2022-03-11

Swift อยู่กับเรามาระยะหนึ่งแล้ว และผ่านการทำซ้ำ ได้นำคุณลักษณะทั้งหมดของภาษาการเขียนโปรแกรมเชิงวัตถุสมัยใหม่มาให้เรา ซึ่งรวมถึงตัวเลือก ยาสามัญ ทูเพิล โครงสร้างที่สนับสนุนเมธอด ส่วนขยายและโปรโตคอล และอื่นๆ อีกมากมาย แต่ถ้าแอปพลิเคชันของคุณใช้ไลบรารี่ที่เขียนโดยใช้ C++ คุณจะไม่สามารถพึ่งพา Swift ได้อีกต่อไป โชคดีที่ Objective-C++ พร้อมให้ความช่วยเหลือเรา

นับตั้งแต่เปิดตัว Swift มีความสามารถในการทำงานร่วมกันที่ยอดเยี่ยมกับ Objective-C และเราจะใช้ Objective-C เพื่อเชื่อมโยง Swift กับ C++ Objective-C++ ไม่มีอะไรมากไปกว่า Objective-C ที่มีความสามารถในการเชื่อมโยงกับโค้ด C++ และในบล็อกโพสต์นี้ เราจะสร้างแอปอย่างง่ายที่จะจดจำโลโก้ Toptal ภายในรูปภาพโดยใช้ OpenCV เมื่อเราตรวจพบโลโก้นั้น เราจะเปิดโฮมเพจ Toptal

ตามที่ระบุไว้ในหน้าเว็บ OpenCV:

"OpenCV ได้รับการออกแบบมาเพื่อประสิทธิภาพในการคำนวณและโดยเน้นที่แอปพลิเคชันแบบเรียลไทม์ เขียนด้วย C/C++ ที่ได้รับการปรับแต่ง ไลบรารีสามารถใช้ประโยชน์จากการประมวลผลแบบมัลติคอร์ได้"

นี่เป็นทางออกที่ดีหากคุณต้องการพัฒนาคอมพิวเตอร์วิทัศน์ที่รวดเร็วและเชื่อถือได้

การสร้าง OpenCV Objective-C++ Wrapper

ในบทช่วยสอนนี้ เราจะออกแบบแอปพลิเคชันที่จะจับคู่โลโก้ Toptal ภายในรูปภาพและเปิดหน้าเว็บ Toptal ในการเริ่มต้น ให้สร้างโปรเจ็กต์ Xcode ใหม่และตั้งค่า CocoaPods โดยใช้ pod init เพิ่ม OpenCV ไปยัง Podfile pod 'OpenCV และเรียกใช้การ pod install ใน Terminal อย่าลืม uncomment use_frameworks ! คำสั่งภายใน Podfile

ตอนนี้เมื่อเรามี OpenCV ภายในโปรเจ็กต์ Xcode เราต้องเชื่อมต่อกับ Swift นี่คือโครงร่างโดยย่อของขั้นตอนที่เกี่ยวข้อง:

ขั้นตอนที่ 1: สร้างคลาส Objective-C ใหม่ OpenCVWrapper เมื่อ Xcode ถามคุณว่า “ คุณต้องการกำหนดค่าส่วนหัวบริดจ์ Objective-C หรือไม่? ” เลือก “ สร้างส่วนหัวของการเชื่อมโยง ” ส่วนหัวของการเชื่อมโยงเป็นที่ที่คุณนำเข้าคลาส Objective-C จากนั้นจะมองเห็นได้ใน Swift

ขั้นตอนที่ 2: เพื่อที่จะใช้ C++ ภายใน Objective-C เราต้องเปลี่ยนนามสกุลไฟล์จาก OpenCVWrapper.m เป็น OpenCVWrapper.mm คุณสามารถทำได้โดยเพียงแค่เปลี่ยนชื่อไฟล์ภายในตัวนำทางโปรเจ็กต์ของ Xcode การเพิ่ม .mm เป็นนามสกุลจะเปลี่ยนประเภทไฟล์จาก Objective-C เป็น Objective-C++

ขั้นตอนที่ 3: นำเข้า OpenCV ลงใน OpenCVWrapper.mm โดยใช้การนำเข้าต่อไปนี้ สิ่งสำคัญคือต้องเขียนการนำเข้าที่ให้ไว้เหนือ #import "OpenCVWrapper.h" เพราะวิธีนี้ช่วยให้เราหลีกเลี่ยงข้อขัดแย้ง BOOL ที่รู้จักกันดีได้ OpenCV มี ​​enum ที่มีค่า NO ซึ่งทำให้เกิดความขัดแย้งกับค่า Objective-C BOOL NO หากคุณไม่ต้องการคลาสที่ใช้ enum เช่นนั้น นี่เป็นวิธีที่ง่ายที่สุด มิฉะนั้น คุณต้องกำหนด BOOL ก่อนนำเข้า OpenCV

 #ifdef __cplusplus #import <opencv2/opencv.hpp> #import <opencv2/imgcodecs/ios.h> #import <opencv2/videoio/cap_ios.h> #endif

ขั้นตอนที่ 4: เพิ่ม #import "OpenCVWrapper.h" ไปที่ส่วนหัวการเชื่อมโยงของคุณ

เปิด OpenCVWrapper.mm และสร้างอินเทอร์เฟซส่วนตัวที่เราจะประกาศคุณสมบัติส่วนตัว:

 @interface OpenCVWrapper() <CvVideoCameraDelegate> @property (strong, nonatomic) CvVideoCamera *videoCamera; @property (assign, nonatomic) cv::Mat logoSample; @end

ในการสร้าง CvVideoCamera เราต้องส่ง UIImageView ไปให้ และเราจะดำเนินการผ่านตัวกำหนดค่าเริ่มต้นของเรา

 - (instancetype)initWithParentView:(UIImageView *)parentView delegate:(id<OpenCVWrapperDelegate>)delegate { if (self = [super init]) { self.delegate = delegate; parentView.contentMode = UIViewContentModeScaleAspectFill; self.videoCamera = [[CvVideoCamera alloc] initWithParentView:parentView]; self.videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack; self.videoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPresetHigh; self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait; self.videoCamera.defaultFPS = 30; self.videoCamera.grayscaleMode = [NSNumber numberWithInt:0].boolValue; self.videoCamera.delegate = self; // Convert UIImage to Mat and store greyscale version UIImage *templateImage = [UIImage imageNamed:@"toptal"]; cv::Mat templateMat; UIImageToMat(templateImage, templateMat); cv::Mat grayscaleMat; cv::cvtColor(templateMat, grayscaleMat, CV_RGB2GRAY); self.logoSample = grayscaleMat; [self.videoCamera start]; } return self; }

ภายในนั้น เรากำหนดค่า CvVideoCamera ที่แสดงผลวิดีโอภายใน parentView ที่กำหนด และจะส่งรูปภาพ cv::Mat มาให้เราวิเคราะห์ผ่านผู้รับมอบสิทธิ์

processImage: เมธอดมาจากโปรโตคอล CvVideoCameraDelegate และภายในนั้น เราจะทำการจับคู่เทมเพลต

 - (void)processImage:(cv::Mat&)image { cv::Mat gimg; // Convert incoming img to greyscale to match template cv::cvtColor(image, gimg, CV_BGR2GRAY); // Get matching cv::Mat res(image.rows-self.logoSample.rows+1, self.logoSample.cols-self.logoSample.cols+1, CV_32FC1); cv::matchTemplate(gimg, self.logoSample, res, CV_TM_CCOEFF_NORMED); cv::threshold(res, res, 0.5, 1., CV_THRESH_TOZERO); double minval, maxval, threshold = 0.9; cv::Point minloc, maxloc; cv::minMaxLoc(res, &minval, &maxval, &minloc, &maxloc); // Call delegate if match is good enough if (maxval >= threshold) { // Draw a rectangle for confirmation cv::rectangle(image, maxloc, cv::Point(maxloc.x + self.logoSample.cols, maxloc.y + self.logoSample.rows), CV_RGB(0,255,0), 2); cv::floodFill(res, maxloc, cv::Scalar(0), 0, cv::Scalar(.1), cv::Scalar(1.)); [self.delegate openCVWrapperDidMatchImage:self]; } }

อันดับแรก เราแปลงรูปภาพที่กำหนดเป็นรูปภาพระดับสีเทาเพราะในวิธี init เราแปลงเทมเพลตโลโก้ Toptal ที่จับคู่รูปภาพเป็นระดับสีเทา ขั้นตอนต่อไปคือการตรวจสอบการจับคู่ที่มีเกณฑ์ที่แน่นอนซึ่งจะช่วยเราในการปรับขนาดและมุม เราต้องตรวจสอบมุมเพราะคุณสามารถถ่ายภาพในมุมต่างๆ ที่เรายังต้องการตรวจจับโลโก้ หลังจากถึงเกณฑ์ที่กำหนด เราจะเรียกผู้รับมอบสิทธิ์และเปิดหน้าเว็บของ Toptal

อย่าลืมเพิ่ม NSCameraUsageDescription ใน Info.plist ของคุณ มิฉะนั้น แอปพลิเคชันของคุณจะขัดข้องทันทีหลังจากโทร [self.videoCamera start]; .

ตอนนี้ ในที่สุด Swift

จนถึงตอนนี้ เรากำลังมุ่งเน้นไปที่ Objective-C++ เพราะตรรกะทั้งหมดอยู่ในนั้น รหัส Swift ของเราจะค่อนข้างง่าย:

  1. ภายใน ViewController.swift ให้สร้าง UIImageView โดยที่ CvVideoCamera จะแสดงเนื้อหา
  2. สร้างอินสแตนซ์ของ OpenCVWrapper และส่งผ่านอินสแตนซ์ UIImageView ไป
  3. ใช้โปรโตคอล OpenCVWrapperDelegate เพื่อเปิดหน้าเว็บของ Toptal เมื่อเราตรวจพบโลโก้
 class ViewController: UIViewController { var wrapper: OpenCVWrapper! @IBOutlet var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. wrapper = OpenCVWrapper.init(parentView: imageView, delegate: self) } } extension ViewController: OpenCVWrapperDelegate { //MARK: - OpenCVWrapperDelegate func openCVWrapperDidMatchImage(_ wrapper: OpenCVWrapper) { UIApplication.shared.open(URL.init(string: "https://toptal.com")!, options: [:], completionHandler: nil) } }

OpenCV Swift ในการดำเนินการ

ในบทความนี้ เราได้แสดงวิธีที่คุณสามารถรวมโค้ด C++ กับ Swift และสร้างคลาส wrapper ที่อยู่ที่นี่เพื่อเชื่อมโยงโค้ด C++ กับ Swift ตอนนี้ เมื่อเราตรวจพบโลโก้ Toptal ผ่านกล้อง เราก็เปิดหน้าแรกของ Toptal

การตรวจจับโลโก้ของ Toptal ด้วย OpenCV Swift

สำหรับการอัปเดตในอนาคต คุณอาจต้องการเรียกใช้การจับคู่เทมเพลต CV ในเธรดพื้นหลัง ด้วยวิธีนี้ คุณจะไม่บล็อกเธรดหลักและ UI จะยังคงตอบสนอง เนื่องจากนี่เป็นเพียงตัวอย่างง่ายๆ ของ OpenCV การจับคู่เทมเพลตอาจไม่ประสบความสำเร็จเป็นพิเศษ แต่บทความนี้มีจุดประสงค์เพื่อแสดงให้คุณเห็นว่าคุณสามารถเริ่มใช้งานได้อย่างไร

หากคุณยังคงสนใจในการเรียนรู้ OpenCV และการใช้งานที่ซับซ้อนมากขึ้นใน iOS ฉันขอแนะนำ Real-time Object Detection โดยใช้ MSER ใน iOS ซึ่งจะแนะนำคุณเกี่ยวกับการตรวจจับภาพโดยใช้กล้องด้านหลังของ iPhone