Suck the cat with code! This article is participating in the cat essay Activity.

Look at the effect

implementation

Camera UI

layout

The camera interface uses camera Review in the dependent camera library to customize the camera interface, and overlay the cat’s head on the camera through the stacked layout to achieve the mask layer effect.

CameraPreview( controller! , ), Container( padding: EdgeInsets.only(top: 150.w), child: Align( alignment: Alignment.topCenter, child: Image.asset( "assets/images/cat.png", width: 280.w, height: 280.w, )), ),Copy the code

Taking pictures

When clicking to take a picture, call the takePicture method of cameraView controller and transform uint8List to generate a picture taken by the current camera.

XFile xFile = await controller! .takePicture(); Uint8List uint8list = await xFile.readAsBytes();Copy the code

Capture the head part of the mask

In fact, the above photo is the same as taking a picture with the system camera. The design of the cat head mask is to obtain the image information of a more accurate position and eliminate the interference of other factors except the middle of the main picture. Therefore, the head part needs to be deducted from the whole photo. The realization method is to intercept and draw the UI. image object through the canvas, and then use the CustomPaint component to draw the captured picture on the interface, and finally obtain the screenshot through the RepaintBoundary component and save it. Note that the hierarchical layout is used to draw the captured image to the bottom of the interface, which is just for screenshots and not visible to the user.

Class ImageClipper extends CustomPainter {final UI.Image Image; ImageClipper(this.image); @override void paint(Canvas canvas, Size size) { Paint paint = Paint(); Canvas.drawImageRect(image, rect. fromLTRB(image.width * 0.12, image.height * 0.25, image.width * 0.88, Height * 0.25 + (image.width * 0.76)), rect. fromLTWH(0, 0, sie.width, sie.height), paint); } @override bool shouldRepaint(CustomPainter oldDelegate) { return false; }}Copy the code

Screenshot component:

RepaintBoundary(key: _globalKey, // Widget used to capture the position of the box, not visible to the user child: Container(width: 200, height: 200, child: CustomPaint( painter: ImageClipper(image!) ,),),)Copy the code

Click to take a picture:

Void _takePhoto() async{if (! (controller? .value.isInitialized ?? false)) { return; } if (controller! .value.isTakingPicture) { return; } XFile xFile = await controller! .takePicture(); Uint8List uint8list = await xFile.readAsBytes(); ui.Codec codec = await ui.instantiateImageCodec(uint8list); ui.FrameInfo fi = await codec.getNextFrame(); setState(() { image = fi.image; }); Future.delayed(Duration(milliseconds: 100), () async { RenderRepaintBoundary boundary = _globalKey.currentContext! .findRenderObject() as RenderRepaintBoundary; Var image = await boundary. ToImage (pixelRatio: 3.0); ByteData? byteData = await image.toByteData(format: ImageByteFormat.png); Uint8List? _photoBytes = byteData? .buffer.asUint8List(); if(_photoBytes == null){ return; } Directory directory = await getTemporaryDirectory(); Directory tempDirectory = Directory(directory.path + "/catIdentifyTemp"); bool hasCreateDirectory = tempDirectory.existsSync(); // create folder if (! hasCreateDirectory) { tempDirectory.createSync(); } String targetPath = directory.path + "/${DateTime.now().millisecondsSinceEpoch}.png"; File tempFile = File(targetPath); bool hasFile = tempFile.existsSync(); // Create temporary file if (! hasFile) { tempFile.createSync(); } // Save the screenshot to the local cache folder File newFile = await tempfile.writeasBytes (_photoBytes); String bs64 = base64Encode(await newFile.readAsBytes()); Navigator.of(context).pop(bs64); }); // var result = await FlutterImageCompress.compressWithList( // uint8list, // quality: 60, // ); }Copy the code

Step pit: In the photo taking interface, the APP will throw an exception when it is in the background and then goes back to the foreground, so it listens to the switch between the front and back of the APP, releases the camera when it is in the background, and re-initializes the camera when it comes back to the foreground.

@override void didChangeAppLifecycleState(ui.AppLifecycleState state) { // App state changed before we got the chance to Initialize. / / set the switch to the background is the purpose of the life cycle to monitor the release of the camera, after cutting back to the front desk to initialize camera, can appear otherwise can't take photos problem if (controller = = null | |! controller! .value.isInitialized) { return; } if (state == AppLifecycleState.inactive) { controller? .dispose(); } else if (state == AppLifecycleState.resumed) { if (controller ! = null) { onNewCameraSelected(controller! .description); } } super.didChangeAppLifecycleState(state); }Copy the code

Image recognition

The image recognition used in the software is Baidu image recognition Api. The interface provided in the Api is to transmit parameters by transferring pictures base64 or picture network address URL, so the software uses Base64 to transmit parameters, and the above code also shows the way to generate Base64 pictures. Before invoking the image recognition interface, the application for Api call token is required. Since the token is valid for 30 days, it is only requested once when the App is started, and this token has been used since then. The display page of identification results is displayed according to the data set returned by the interface, including fields such as matching degree and Baidu Encyclopedia introduction.

In addition, this Baidu interface is for animal recognition, not only can recognize cats, but also other animals, the following is the recognition of dogs:

The complete code

Project address → Note that students after clone need to add the application on Baidu AI platform by themselves, get the key and key of the APP and paste it into the ApiConstants class of the project.