Group Group Group Group Group Group Group Group Group

Vision - convert UIImage to pure Black&White and detect DataMatrix

I really need help. I’m creating a DataMatrix reader, and part of codes are with white background only and causes any problem with AVFoundation, but another part has grey with shimmer background (see image below), and this driving me crazy.

What I’ve tried:

  1. AVFoundation with its metaDataOutput works perfect only with white background, and there was no success with shimmer grey

2)zxing - actually can’t find any working example for swift, and their sample from GitHub find no Datamatrix on grey too (with datamatrix as qr code type), will be thankful for tutorial or smth like this (zxingObjC for Swift)

3)about 20 libs from cocoapods/github - nothing with grey back again

  1. then I found that Vision perfectly detect Datamatrix on white from photo, so I decided to work with this lib and changed the way: no more catching a video output, only UIImages, then handle them and detect DataMatrix using Vision framework.

And to convert colors I’ve tried:

CIFilters (ColorsControls, NoirEffect), GPU filters (monochrome, luminance, averageLuminance,adaptiveTreshold) playing with params

In the end I have no solution that will work 10 from 10 with my DataMatrix stickers. sometimes it works with GPUImageAverageLuminanceThresholdFilter and GPUImageAdaptiveThresholdFilter , but about 20% luck.

And this 20% luck only at daylight, with electric light comes shimmer-glitter, I think.

Any advice will be helpful for me! Maybe there is nice solution with Zxing for Swift, which I can’t find. Or there is no need to use Vision and get frames from AVFoundation, but how?

I-nigma etc. catch my stickers perfectly, from live video, so there should be the way. Android version of my scanner use Zxing, and I guess that Zxing do the job…


My sheme:

    fileprivate func createSession(input:AVCaptureDeviceInput) -> Bool {
        let session = AVCaptureSession()        
        if session.canAddInput(input) {
            session.addInput(input)
        } else {
            return false
        }        
        let output = AVCaptureVideoDataOutput()
        if session.canAddOutput(output) {
            output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "com.output"))
            output.videoSettings = [String(kCVPixelBufferPixelFormatTypeKey):kCVPixelFormatType_32BGRA]
            output.alwaysDiscardsLateVideoFrames = true            
            session.addOutput(output)
        }
        self.videoSession = session
        return true
    }

extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    func convert(cmage:CIImage) -> UIImage
    {
        let context:CIContext = CIContext.init(options: nil)
        let cgImage:CGImage = context.createCGImage(cmage, from: cmage.extent)!
        let image:UIImage = UIImage.init(cgImage: cgImage)
        return image
    }

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

        let threshold:Double = 1.0 / 3
        let timeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
        currentTime = Double(timeStamp.value) / Double(timeStamp.timescale)
        if (currentTime - lastTime > threshold) {

            if let image = imageFromSampleBuffer(sampleBuffer: sampleBuffer),
                let cgImage = image.cgImage {

                let ciImage = CIImage(cgImage: cgImage)
//                with CIFilter
//                let blackAndWhiteImage = ciImage.applyingFilter("CIColorControls", parameters: [kCIInputContrastKey: 2.5,
//                                                                                                kCIInputSaturationKey: 0,
//                                                                                                kCIInputBrightnessKey: 0.5]) 
//                let imageToScan = convert(cmage: blackAndWhiteImage) //UIImage(ciImage: blackAndWhiteImage)
//                resImage = imageToScan
//                scanBarcode(cgImage: imageToScan.cgImage!)


                let filter = GPUImageAverageLuminanceThresholdFilter()
                filter.thresholdMultiplier = 0.7
                let imageToScan = filter.image(byFilteringImage: image)
                resImage = imageToScan!
                scanBarcode(cgImage: imageToScan!.cgImage!)
            }
        }
    }

    fileprivate func imageFromSampleBuffer(sampleBuffer : CMSampleBuffer) -> UIImage? {
        guard let imgBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            return nil
        }
        // Lock the base address of the pixel buffer
        CVPixelBufferLockBaseAddress(imgBuffer, CVPixelBufferLockFlags.readOnly)

        // Get the number of bytes per row for the pixel buffer
        let baseAddress = CVPixelBufferGetBaseAddress(imgBuffer)

        // Get the number of bytes per row for the pixel buffer
        let bytesPerRow = CVPixelBufferGetBytesPerRow(imgBuffer)
        // Get the pixel buffer width and height
        let width = CVPixelBufferGetWidth(imgBuffer)
        let height = CVPixelBufferGetHeight(imgBuffer)

        // Create a device-dependent RGB color space
        let colorSpace = CGColorSpaceCreateDeviceRGB()

        // Create a bitmap graphics context with the sample buffer data
        var bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue
        bitmapInfo |= CGImageAlphaInfo.premultipliedFirst.rawValue & CGBitmapInfo.alphaInfoMask.rawValue
        //let bitmapInfo: UInt32 = CGBitmapInfo.alphaInfoMask.rawValue
        let context = CGContext.init(data: baseAddress, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo)
        // Create a Quartz image from the pixel data in the bitmap graphics context
        let quartzImage = context?.makeImage()
        // Unlock the pixel buffer
        CVPixelBufferUnlockBaseAddress(imgBuffer, CVPixelBufferLockFlags.readOnly)

        CVPixelBufferLockBaseAddress(imgBuffer, .readOnly)

        if var image = quartzImage {
            if shouldInvert, let inverted = invertImage(image) {
                image = inverted
            }
            let output = UIImage(cgImage: image)
            return output
        }
        return nil
    }


    fileprivate func scanBarcode(cgImage: CGImage) {
        let barcodeRequest = VNDetectBarcodesRequest(completionHandler: { request, _ in
            self.parseResults(results: request.results)
        })
        let handler = VNImageRequestHandler(cgImage: cgImage, options: [.properties : ""])
        guard let _ = try? handler.perform([barcodeRequest]) else {
            return print("Could not scan")
        }
    }

    fileprivate func parseResults(results: [Any]?) {
        guard let results = results else {
            return print("No results")
        }
        print("GOT results - ", results.count)
        for result in results {
            if let barcode = result as? VNBarcodeObservation {
                if let code = barcode.payloadStringValue {
                    DispatchQueue.main.async {
                        self.videoSession?.stopRunning()
                        self.resultLabel.text = code
                        self.blackWhiteImageView.image = self.resImage //just to check from what image code scanned
                    }
                } else {
                    print("No results 2")
                }
            } else {
                print("No results 1")
            }
        }
    }
}

@mact Do you still have issues with this?