Swift之二維碼的生成、識別和掃描

掃碼關注簡書地址:

簡書地址.png

最近在項目中遇到了涉及二維碼相關的問題, 這裏想記錄總結一下二維碼相關技術git

一. 二維碼的介紹

  • 二維條碼/二維碼是用某種特定的幾何圖形按必定規律在平面分佈的黑白相間的圖形記錄數據符號信息的
  • 總結: 用圖形記錄標記一些信息,方便經過圖形識別來獲取信息
  • 應用場景
    • 信息獲取(名片、地圖、WIFI密碼、資料)
    • 手機電商(用戶掃碼、手機直接購物下單)
    • 手機支付(掃描商品二維碼,經過銀行或第三方支付提供的手機端通道完成支付)
    • 微信添加好友

二. 二維碼的生成

  • 生成二維碼的方式
    • 採用第三方框架(放棄)
      • ZXing/ZBar
      • 框架不支持64位(2015年2月1號起, - 不容許不支持64位處理器的APP 上架)
    • 系統自帶API
  • 生成二維碼的步驟
    • 建立二維碼濾鏡--CIFilter
    • 恢復濾鏡的默認屬性
    • 設置濾鏡的輸入數據
    • 將傳入的字符串轉換成Data(OC爲NSData)數據
    • 經過KVC來設置輸入的內容inputMessage

1. 二維碼容錯率

filter?.setValue("H", forKey: "inputCorrectionLevel")
複製代碼
  • inputCorrectionLevel 是一個單字母(@"L", @"M", @"Q", @"H" 中的一個),表示不一樣級別的容錯率,默認爲 @"M".
  • QR碼有容錯能力,QR碼圖形若是有破損,仍然能夠被機器讀取內容,最高能夠到7%~30%面積破損仍可被讀取,相對而言,容錯率愈高,QR碼圖形面積愈大。因此通常折衷使用15%容錯能力。
  • L水平 7%的字碼可被修正.
  • M水平 15%的字碼可被修正
  • Q水平 25%的字碼可被修正
  • H水平 30%的字碼可被修正
  • 代碼:
/* * @param inputMsg 二維碼保存的信息 * @param fgImage 前景圖片 */
func generateCode(inputMsg: String, fgImage: UIImage?) -> UIImage {
    //1. 將內容生成二維碼
    //1.1 建立濾鏡
    let filter = CIFilter(name: "CIQRCodeGenerator")
    
    //1.2 恢復默認設置
    filter?.setDefaults()
    
    //1.3 設置生成的二維碼的容錯率
    //value = @"L/M/Q/H"
    filter?.setValue("H", forKey: "inputCorrectionLevel")
    
    // 2.設置輸入的內容(KVC)
    // 注意:key = inputMessage, value必須是NSData類型
    let inputData = inputMsg.data(using: .utf8)
    filter?.setValue(inputData, forKey: "inputMessage")
    
    //3. 獲取輸出的圖片
    guard let outImage = filter?.outputImage else { return UIImage() }
    
    //4. 獲取高清圖片
    let hdImage = getHDImage(outImage)
    
    //5. 判斷是否有前景圖片
    if fgImage == nil{
        return hdImage
    }
    
    //6. 獲取有前景圖片的二維碼
    return getResultImage(hdImage: hdImage, fgImage: fgImage!)
}

複製代碼

2. 獲取高清圖片

//4. 獲取高清圖片
fileprivate func getHDImage(_ outImage: CIImage) -> UIImage {
    let transform = CGAffineTransform(scaleX: 10, y: 10)
    //放大圖片
    let ciImage = outImage.transformed(by: transform)
    
    return UIImage(ciImage: ciImage)
}

複製代碼

3. 將圖片合成到二維碼中

  • 須要用到圖形上下文
  • 將二維碼畫到圖形上下文
  • 將圖片合成到圖行上下文
//獲取前景圖片
fileprivate func getResultImage(hdImage: UIImage, fgImage: UIImage) -> UIImage {
    let hdSize = hdImage.size
    //1. 開啓圖形上下文
    UIGraphicsBeginImageContext(hdSize)
    
    //2. 將高清圖片畫到上下文
    hdImage.draw(in: CGRect(x: 0, y: 0, width: hdSize.width, height: hdSize.height))
    
    //3. 將前景圖片畫到上下文
    let fgWidth: CGFloat = 80
    fgImage.draw(in: CGRect(x: (hdSize.width - fgWidth) / 2, y: (hdSize.height - fgWidth) / 2, width: fgWidth, height: fgWidth))
    
    //4. 獲取上下文
    guard let resultImage = UIGraphicsGetImageFromCurrentImageContext() else { return UIImage() }
    
    //5. 關閉上下文
    UIGraphicsEndImageContext()
    
    return resultImage
}

複製代碼

後續會研究彩色二維碼的黑科技, 敬請期待...github

識別二維碼

識別圖片中二維碼步驟數組

  • 建立探測器
    • 屬於CoreImage框架(CIDetector)
  • 獲取CIImage類型的圖片
  • 獲取圖片中全部符合特徵的內容(CIQRCodeFeature)
  • 遍歷全部的特性(CIQRCodeFeature)
  • 獲取特徵中表明的信息(messageString)
  • 識別二維碼的代碼實現
/* * @param qrCodeImage 二維碼的圖片 * @return 結果的數組 */
func recognitionQRCode(qrCodeImage: UIImage) -> [String]? {
    //1. 建立過濾器
    let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: nil)
    
    //2. 獲取CIImage
    guard let ciImage = CIImage(image: qrCodeImage) else { return nil }
    
    //3. 識別二維碼
    guard let features = detector?.features(in: ciImage) else { return nil }
    
    //4. 遍歷數組, 獲取信息
    var resultArr = [String]()
    for feature in features {
        resultArr.append(feature.type)
    }
    
    return resultArr
}

複製代碼

三. 二維碼的掃描

  • 建立輸入設備(攝像頭)
    • 獲取攝像頭設備
    • 建立輸入對象
  • 建立輸出設置(元數據)
    • 建立輸出對象
    • 設置輸出對象的代理(在代理中獲取掃描到的數據)
    • 設置輸出數據的類型
  • 建立捕捉會話
    • 將輸入添加到會話中
    • 將輸出添加到會話中
  • 添加預覽圖片(方便用於查看)
    • 建立圖層,將圖片添加到View圖層中
  • 開始掃描

1. 懶加載輸入輸出中間會話

//輸入輸出中間橋樑(會話)
fileprivate lazy var session : AVCaptureSession = AVCaptureSession()
複製代碼

2. 初始化掃描設備

2.1. 注意: AVCaptureMetadataOutputObjectsDelegate的代理設置, 該協議中的方法會將掃描的結果返回

fileprivate func addScaningVideo(){
    //1.獲取輸入設備(攝像頭)
    guard let device = AVCaptureDevice.default(for: .video) else { return }
    
    //2.根據輸入設備建立輸入對象
    guard let deviceInput = try? AVCaptureDeviceInput(device: device) else { return }
    
    //3.建立原數據的輸出對象
    let metadataOutput = AVCaptureMetadataOutput()
    
    //4.設置代理監聽輸出對象輸出的數據,在主線程中刷新
    metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
    
    //5.建立會話(橋樑)
    // let session = AVCaptureSession()
    
    //6.添加輸入和輸出到會話
    if session.canAddInput(deviceInput) {
        session.addInput(deviceInput)
    }
    if session.canAddOutput(metadataOutput) {
        session.addOutput(metadataOutput)
    }
    
    //7.告訴輸出對象要輸出什麼樣的數據(二維碼仍是條形碼),要先建立會話才能設置
    metadataOutput.metadataObjectTypes = [.qr, .code128, .code39, .code93, .code39Mod43, .ean8, .ean13, .upce, .pdf417, .aztec]
    
    //8.建立預覽圖層
    let previewLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: session)
    previewLayer.videoGravity = .resizeAspectFill
    previewLayer.frame = view.bounds
    view.layer.insertSublayer(previewLayer, at: 0)
    
    //9.設置有效掃描區域(默認整個屏幕區域)(每一個取值0~1, 以屏幕右上角爲座標原點)
    let rect = CGRect(x: scanImageView.frame.minY / kScreenHeight, y: scanImageView.frame.minX / kScreenWidth, width: scanImageView.frame.height / kScreenHeight, height: scanImageView.frame.width / kScreenWidth)
    metadataOutput.rectOfInterest = rect
    
    //10. 開始掃描
    session.startRunning()
}

複製代碼

2.2 代理方法的實現

  • 須要將掃描的結果轉化成機器可讀的編碼數據,才能獲取二維碼的相關信息
extension ScaningViewController: AVCaptureMetadataOutputObjectsDelegate {
    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        //1. 取出掃描到的數據: metadataObjects
        //2. 以震動的形式告知用戶掃描成功
        AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
        
        //3. 關閉session
        session.stopRunning()
        
        //4. 遍歷結果
        var resultArr = [String]()
        for result in metadataObjects {
            //轉換成機器可讀的編碼數據
            if let code = result as? AVMetadataMachineReadableCodeObject {
                resultArr.append(code.stringValue ?? "")
            }else {
                resultArr.append(result.type.rawValue)
            }
        }
        
        //5. 將結果
        let vc = ShowViewController()
        vc.scanDataArr = resultArr
        navigationController?.pushViewController(vc, animated: true)
    }
}

複製代碼

項目地址: Github

  • 感謝你們的支持

其餘相關文章

相關文章
相關標籤/搜索