Merge 1001ead358 into ec43bea176
				
					
				
			This commit is contained in:
		
						commit
						5261d1d966
					
				|  | @ -390,6 +390,14 @@ http_archive( | ||||||
|     url = "https://github.com/opencv/opencv/releases/download/3.2.0/opencv-3.2.0-ios-framework.zip", |     url = "https://github.com/opencv/opencv/releases/download/3.2.0/opencv-3.2.0-ios-framework.zip", | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | # http_archive( | ||||||
|  | #     name = "ios_opencv", | ||||||
|  | #     sha256 = "c23e92c4a0cd343f73d4056e66c961cdf68e73ca699b1129638537a931c4ffc8", | ||||||
|  | #     build_file = "@//third_party:opencv_ios.BUILD", | ||||||
|  | #     type = "zip", | ||||||
|  | #     url = "https://github.com/opencv/opencv/releases/download/4.7.0/opencv-4.7.0-ios-framework.zip", | ||||||
|  | # ) | ||||||
|  | 
 | ||||||
| # Building an opencv.xcframework from the OpenCV 4.5.3 sources is necessary for | # Building an opencv.xcframework from the OpenCV 4.5.3 sources is necessary for | ||||||
| # MediaPipe iOS Task Libraries to be supported on arm64(M1) Macs. An | # MediaPipe iOS Task Libraries to be supported on arm64(M1) Macs. An | ||||||
| # `opencv.xcframework` archive has not been released and it is recommended to | # `opencv.xcframework` archive has not been released and it is recommended to | ||||||
|  |  | ||||||
							
								
								
									
										33
									
								
								build_face_mesh_ios_framework.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								build_face_mesh_ios_framework.sh
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | ||||||
|  | #!/bin/sh | ||||||
|  | 
 | ||||||
|  | sudo rm -rf frameworkbuild | ||||||
|  | 
 | ||||||
|  | # Create output directories~ | ||||||
|  | mkdir -p ./frameworkbuild/FaceMeshSDK/arm64 | ||||||
|  | # XCFramework is how we're going to use it. | ||||||
|  | mkdir -p ./frameworkbuild/FaceMeshSDK/xcframework | ||||||
|  | 
 | ||||||
|  | # Interesting fact. Bazel `build` command stores cached files in `/private/var/tmp/...` folders | ||||||
|  | # and when you run build, if it finds cached files, it kind of symlinks the files/folders | ||||||
|  | # into the `bazel-bin` folder found in the project root. So don't be afraid of re-running builds | ||||||
|  | # because the files are cached. | ||||||
|  | 
 | ||||||
|  | # build the arm64 binary framework | ||||||
|  | # bazel build --copt=-fembed-bitcode --apple_bitcode=embedded --config=ios_arm64 mediapipe/examples/ios/facemeshioslib:FaceMeshIOSLibFramework | ||||||
|  | bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/facemeshioslib:FaceMeshSDK | ||||||
|  | # use --cxxopt=-O3 to reduce framework size | ||||||
|  | # bazel build --copt=-O3 --cxxopt=-O3 --config=ios_arm64 mediapipe/examples/ios/facemeshioslib:FaceMeshIOSLibFramework | ||||||
|  | 
 | ||||||
|  | # The arm64 framework zip will be located at //bazel-bin/mediapipe/examples/ios/facemeshioslib/FaceMeshIOSLibFramework.zip | ||||||
|  | 
 | ||||||
|  | # Call the framework patcher (First argument = compressed framework.zip, Second argument = header file's name(in this case FaceMeshIOSLib.h)) | ||||||
|  | sudo bash ./mediapipe/examples/ios/facemeshioslib/patch_ios_framework.sh ./bazel-bin/mediapipe/examples/ios/facemeshioslib/FaceMeshSDK.zip FaceMesh.h | ||||||
|  | 
 | ||||||
|  | # There will be a resulting patched .framework folder at the same directory, this is our arm64 one, we copy it to our arm64 folder | ||||||
|  | sudo cp -a ./bazel-bin/mediapipe/examples/ios/facemeshioslib/FaceMeshSDK.framework ./frameworkbuild/FaceMeshSDK/arm64 | ||||||
|  | 
 | ||||||
|  | # Create xcframework (because the classic lipo method with normal .framework no longer works (shows Building for iOS Simulator, but the linked and embedded framework was built for iOS + iOS Simulator)) | ||||||
|  | 
 | ||||||
|  | sudo xcodebuild -create-xcframework \ | ||||||
|  |     -framework ./frameworkbuild/FaceMeshSDK/arm64/FaceMeshSDK.framework \ | ||||||
|  |     -output ./frameworkbuild/FaceMeshSDK/xcframework/FaceMeshSDK.xcframework | ||||||
							
								
								
									
										
											BIN
										
									
								
								frameworkbuild/FaceMeshSDK/arm64/FaceMeshSDK.framework/FaceMeshSDK
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frameworkbuild/FaceMeshSDK/arm64/FaceMeshSDK.framework/FaceMeshSDK
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -0,0 +1,58 @@ | ||||||
|  | #import <CoreVideo/CoreVideo.h> | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  | 
 | ||||||
|  | @interface IntPoint : NSObject | ||||||
|  | @property(nonatomic) NSInteger x; | ||||||
|  | @property(nonatomic) NSInteger y; | ||||||
|  | 
 | ||||||
|  | - (instancetype)initWithX:(NSInteger)x y:(NSInteger)y; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface NSValue (IntPoint) | ||||||
|  | + (instancetype)valuewithIntPoint:(IntPoint *)value; | ||||||
|  | @property (readonly) IntPoint* intPointValue; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface FaceMeshLandmarkPoint : NSObject | ||||||
|  | @property(nonatomic) float x; | ||||||
|  | @property(nonatomic) float y; | ||||||
|  | @property(nonatomic) float z; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface FaceMeshNormalizedRect : NSObject | ||||||
|  | @property(nonatomic) float centerX; | ||||||
|  | @property(nonatomic) float centerY; | ||||||
|  | @property(nonatomic) float height; | ||||||
|  | @property(nonatomic) float width; | ||||||
|  | @property(nonatomic) float rotation; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @protocol FaceMeshDelegate <NSObject> | ||||||
|  | @optional | ||||||
|  | /**
 | ||||||
|  |  * Array of faces, with faces represented by arrays of face landmarks | ||||||
|  |  */ | ||||||
|  | - (void)didReceiveFaces:(NSArray<NSArray<FaceMeshLandmarkPoint *> *> *)faces; | ||||||
|  | 
 | ||||||
|  | - (void)didSavedRegions:(NSArray<NSURL *> *)foreheadURLs | ||||||
|  |           leftcheekURLs:(NSArray<NSURL *> *)leftcheekURLs | ||||||
|  |           rightcheekURLs:(NSArray<NSURL *> *)rightcheekURLs; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface FaceMesh : NSObject | ||||||
|  | - (instancetype)init; | ||||||
|  | - (void)startGraph; | ||||||
|  | - (void)processVideoFrame:(CVPixelBufferRef)imageBuffer; | ||||||
|  | - (CVPixelBufferRef)resize:(CVPixelBufferRef)pixelBuffer | ||||||
|  |                      width:(int)width | ||||||
|  |                     height:(int)height; | ||||||
|  | - (uint8_t **)buffer2Array2D:(CVPixelBufferRef)pixelBuffer; | ||||||
|  | - (void)extractRegions:(NSURL *)fileName | ||||||
|  |             foreheadBoxes:(NSArray<NSArray<IntPoint *> *> *)foreheadBoxes | ||||||
|  |             leftCheekBoxes:(NSArray<NSArray<IntPoint *> *> *)leftCheekBoxes | ||||||
|  |             rightCheekBoxes:(NSArray<NSArray<IntPoint *> *> *)rightCheekBoxes | ||||||
|  |             totalFramesNeedProcess:(NSInteger)totalFramesNeedProcess | ||||||
|  |             skipNFirstFrames:(NSInteger)skipNFirstFrames; | ||||||
|  | @property(weak, nonatomic) id<FaceMeshDelegate> delegate; | ||||||
|  | @property(nonatomic) size_t timestamp; | ||||||
|  | @end | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | //
 | ||||||
|  | //  FaceMeshSDK.h
 | ||||||
|  | //  FaceMeshSDK
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  | 
 | ||||||
|  | //! Project version number for FaceMeshSDK.
 | ||||||
|  | FOUNDATION_EXPORT double FaceMeshSDKVersionNumber; | ||||||
|  | 
 | ||||||
|  | //! Project version string for FaceMeshSDK.
 | ||||||
|  | FOUNDATION_EXPORT const unsigned char FaceMeshSDKVersionString[]; | ||||||
|  | 
 | ||||||
|  | // In this header, you should import all the public headers of your framework using statements like #import <FaceMeshSDK/PublicHeader.h>
 | ||||||
|  | 
 | ||||||
|  | #import "FaceMesh.h" | ||||||
							
								
								
									
										
											BIN
										
									
								
								frameworkbuild/FaceMeshSDK/arm64/FaceMeshSDK.framework/Info.plist
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frameworkbuild/FaceMeshSDK/arm64/FaceMeshSDK.framework/Info.plist
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | framework module FaceMeshSDK { | ||||||
|  |   umbrella header "FaceMeshSDK.h" | ||||||
|  | 
 | ||||||
|  |   export * | ||||||
|  |   module * { export * } | ||||||
|  | 
 | ||||||
|  |   link framework "AVFoundation" | ||||||
|  |   link framework "Accelerate" | ||||||
|  |   link framework "AssetsLibrary" | ||||||
|  |   link framework "CoreFoundation" | ||||||
|  |   link framework "CoreGraphics" | ||||||
|  |   link framework "CoreImage" | ||||||
|  |   link framework "CoreMedia" | ||||||
|  |   link framework "CoreVideo" | ||||||
|  |   link framework "GLKit" | ||||||
|  |   link framework "Metal" | ||||||
|  |   link framework "MetalKit" | ||||||
|  |   link framework "OpenGLES" | ||||||
|  |   link framework "QuartzCore" | ||||||
|  |   link framework "UIKit" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								frameworkbuild/FaceMeshSDK/arm64/FaceMeshSDK.framework/face_landmark.tflite
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frameworkbuild/FaceMeshSDK/arm64/FaceMeshSDK.framework/face_landmark.tflite
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | 
 | ||||||
|  | €ConstantSidePacketCalculator2PACKET:with_attentionBI | ||||||
|  | Atype.googleapis.com/mediapipe.ConstantSidePacketCalculatorOptions | ||||||
|  |  | ||||||
|  | ¬FaceLandmarkFrontGpuIMAGE:input_video"LANDMARKS:multi_face_landmarks"-ROIS_FROM_LANDMARKS:face_rects_from_landmarks*NUM_FACES:num_faces*WITH_ATTENTION:with_attentionRinput_videozmulti_face_landmarks‚	num_faces | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | 
 | ||||||
|  | <EFBFBD>FaceLandmarkFrontGpuIMAGE:input_video"LANDMARKS:multi_face_landmarks"-ROIS_FROM_LANDMARKS:face_rects_from_landmarks*NUM_FACES:num_faces | ||||||
|  | SPacketPresenceCalculatorPACKET:multi_face_landmarks"PRESENCE:landmark_presenceRinput_videozmulti_face_landmarkszface_rects_from_landmarkszlandmark_presence‚	num_faces | ||||||
|  | @ -0,0 +1,25 @@ | ||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||
|  | <plist version="1.0"> | ||||||
|  | <dict> | ||||||
|  | 	<key>AvailableLibraries</key> | ||||||
|  | 	<array> | ||||||
|  | 		<dict> | ||||||
|  | 			<key>LibraryIdentifier</key> | ||||||
|  | 			<string>ios-arm64</string> | ||||||
|  | 			<key>LibraryPath</key> | ||||||
|  | 			<string>FaceMeshSDK.framework</string> | ||||||
|  | 			<key>SupportedArchitectures</key> | ||||||
|  | 			<array> | ||||||
|  | 				<string>arm64</string> | ||||||
|  | 			</array> | ||||||
|  | 			<key>SupportedPlatform</key> | ||||||
|  | 			<string>ios</string> | ||||||
|  | 		</dict> | ||||||
|  | 	</array> | ||||||
|  | 	<key>CFBundlePackageType</key> | ||||||
|  | 	<string>XFWK</string> | ||||||
|  | 	<key>XCFrameworkFormatVersion</key> | ||||||
|  | 	<string>1.0</string> | ||||||
|  | </dict> | ||||||
|  | </plist> | ||||||
										
											Binary file not shown.
										
									
								
							|  | @ -0,0 +1,58 @@ | ||||||
|  | #import <CoreVideo/CoreVideo.h> | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  | 
 | ||||||
|  | @interface IntPoint : NSObject | ||||||
|  | @property(nonatomic) NSInteger x; | ||||||
|  | @property(nonatomic) NSInteger y; | ||||||
|  | 
 | ||||||
|  | - (instancetype)initWithX:(NSInteger)x y:(NSInteger)y; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface NSValue (IntPoint) | ||||||
|  | + (instancetype)valuewithIntPoint:(IntPoint *)value; | ||||||
|  | @property (readonly) IntPoint* intPointValue; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface FaceMeshLandmarkPoint : NSObject | ||||||
|  | @property(nonatomic) float x; | ||||||
|  | @property(nonatomic) float y; | ||||||
|  | @property(nonatomic) float z; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface FaceMeshNormalizedRect : NSObject | ||||||
|  | @property(nonatomic) float centerX; | ||||||
|  | @property(nonatomic) float centerY; | ||||||
|  | @property(nonatomic) float height; | ||||||
|  | @property(nonatomic) float width; | ||||||
|  | @property(nonatomic) float rotation; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @protocol FaceMeshDelegate <NSObject> | ||||||
|  | @optional | ||||||
|  | /**
 | ||||||
|  |  * Array of faces, with faces represented by arrays of face landmarks | ||||||
|  |  */ | ||||||
|  | - (void)didReceiveFaces:(NSArray<NSArray<FaceMeshLandmarkPoint *> *> *)faces; | ||||||
|  | 
 | ||||||
|  | - (void)didSavedRegions:(NSArray<NSURL *> *)foreheadURLs | ||||||
|  |           leftcheekURLs:(NSArray<NSURL *> *)leftcheekURLs | ||||||
|  |           rightcheekURLs:(NSArray<NSURL *> *)rightcheekURLs; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface FaceMesh : NSObject | ||||||
|  | - (instancetype)init; | ||||||
|  | - (void)startGraph; | ||||||
|  | - (void)processVideoFrame:(CVPixelBufferRef)imageBuffer; | ||||||
|  | - (CVPixelBufferRef)resize:(CVPixelBufferRef)pixelBuffer | ||||||
|  |                      width:(int)width | ||||||
|  |                     height:(int)height; | ||||||
|  | - (uint8_t **)buffer2Array2D:(CVPixelBufferRef)pixelBuffer; | ||||||
|  | - (void)extractRegions:(NSURL *)fileName | ||||||
|  |             foreheadBoxes:(NSArray<NSArray<IntPoint *> *> *)foreheadBoxes | ||||||
|  |             leftCheekBoxes:(NSArray<NSArray<IntPoint *> *> *)leftCheekBoxes | ||||||
|  |             rightCheekBoxes:(NSArray<NSArray<IntPoint *> *> *)rightCheekBoxes | ||||||
|  |             totalFramesNeedProcess:(NSInteger)totalFramesNeedProcess | ||||||
|  |             skipNFirstFrames:(NSInteger)skipNFirstFrames; | ||||||
|  | @property(weak, nonatomic) id<FaceMeshDelegate> delegate; | ||||||
|  | @property(nonatomic) size_t timestamp; | ||||||
|  | @end | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | //
 | ||||||
|  | //  FaceMeshSDK.h
 | ||||||
|  | //  FaceMeshSDK
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  | 
 | ||||||
|  | //! Project version number for FaceMeshSDK.
 | ||||||
|  | FOUNDATION_EXPORT double FaceMeshSDKVersionNumber; | ||||||
|  | 
 | ||||||
|  | //! Project version string for FaceMeshSDK.
 | ||||||
|  | FOUNDATION_EXPORT const unsigned char FaceMeshSDKVersionString[]; | ||||||
|  | 
 | ||||||
|  | // In this header, you should import all the public headers of your framework using statements like #import <FaceMeshSDK/PublicHeader.h>
 | ||||||
|  | 
 | ||||||
|  | #import "FaceMesh.h" | ||||||
										
											Binary file not shown.
										
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | framework module FaceMeshSDK { | ||||||
|  |   umbrella header "FaceMeshSDK.h" | ||||||
|  | 
 | ||||||
|  |   export * | ||||||
|  |   module * { export * } | ||||||
|  | 
 | ||||||
|  |   link framework "AVFoundation" | ||||||
|  |   link framework "Accelerate" | ||||||
|  |   link framework "AssetsLibrary" | ||||||
|  |   link framework "CoreFoundation" | ||||||
|  |   link framework "CoreGraphics" | ||||||
|  |   link framework "CoreImage" | ||||||
|  |   link framework "CoreMedia" | ||||||
|  |   link framework "CoreVideo" | ||||||
|  |   link framework "GLKit" | ||||||
|  |   link framework "Metal" | ||||||
|  |   link framework "MetalKit" | ||||||
|  |   link framework "OpenGLES" | ||||||
|  |   link framework "QuartzCore" | ||||||
|  |   link framework "UIKit" | ||||||
|  | } | ||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | 
 | ||||||
|  | €ConstantSidePacketCalculator2PACKET:with_attentionBI | ||||||
|  | Atype.googleapis.com/mediapipe.ConstantSidePacketCalculatorOptions | ||||||
|  |  | ||||||
|  | ¬FaceLandmarkFrontGpuIMAGE:input_video"LANDMARKS:multi_face_landmarks"-ROIS_FROM_LANDMARKS:face_rects_from_landmarks*NUM_FACES:num_faces*WITH_ATTENTION:with_attentionRinput_videozmulti_face_landmarks‚	num_faces | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | 
 | ||||||
|  | <EFBFBD>FaceLandmarkFrontGpuIMAGE:input_video"LANDMARKS:multi_face_landmarks"-ROIS_FROM_LANDMARKS:face_rects_from_landmarks*NUM_FACES:num_faces | ||||||
|  | SPacketPresenceCalculatorPACKET:multi_face_landmarks"PRESENCE:landmark_presenceRinput_videozmulti_face_landmarkszface_rects_from_landmarkszlandmark_presence‚	num_faces | ||||||
							
								
								
									
										
											BIN
										
									
								
								mediapipe/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mediapipe/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								mediapipe/examples/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mediapipe/examples/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								mediapipe/examples/ios/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mediapipe/examples/ios/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								mediapipe/examples/ios/facemeshioslib/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mediapipe/examples/ios/facemeshioslib/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										68
									
								
								mediapipe/examples/ios/facemeshioslib/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								mediapipe/examples/ios/facemeshioslib/BUILD
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | ||||||
|  | load( | ||||||
|  |     "@build_bazel_rules_apple//apple:ios.bzl", | ||||||
|  |     "ios_framework" | ||||||
|  | ) | ||||||
|  | load( | ||||||
|  |     "//mediapipe/examples/ios:bundle_id.bzl", | ||||||
|  |     "BUNDLE_ID_PREFIX", | ||||||
|  |     "example_provisioning", | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | licenses(["notice"])  # Apache 2.0 | ||||||
|  | 
 | ||||||
|  | MIN_IOS_VERSION = "11.1" | ||||||
|  | 
 | ||||||
|  | IOS_FAMILIES = [ | ||||||
|  |     "iphone", | ||||||
|  |     "ipad", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | FRAMEWORK_HEADERS = [ | ||||||
|  |     "FaceMesh.h", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | ios_framework( | ||||||
|  |     name = "FaceMeshSDK", | ||||||
|  |     hdrs = FRAMEWORK_HEADERS, | ||||||
|  |     bundle_id = BUNDLE_ID_PREFIX + ".FaceMeshSDK", | ||||||
|  |     bundle_name = "FaceMeshSDK", | ||||||
|  |     families = IOS_FAMILIES, | ||||||
|  |     infoplists = [ | ||||||
|  |         "//mediapipe/examples/ios/common:Info.plist", | ||||||
|  | #        "Info.plist", | ||||||
|  |     ], | ||||||
|  |     minimum_os_version = MIN_IOS_VERSION, | ||||||
|  |     visibility = ["//visibility:public"], | ||||||
|  |     deps = [ | ||||||
|  |         ":FaceMeshObj", | ||||||
|  |         "@ios_opencv//:OpencvFramework", | ||||||
|  |     ], | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | objc_library( | ||||||
|  |     name = "FaceMeshObj", | ||||||
|  |     srcs = [ | ||||||
|  |         "FaceMesh.mm", | ||||||
|  |     ], | ||||||
|  |     hdrs = FRAMEWORK_HEADERS, | ||||||
|  |     copts = ["-std=c++17"], | ||||||
|  |     data = [ | ||||||
|  |         "//mediapipe/graphs/face_mesh:face_mesh_ios_lib_gpu.binarypb", | ||||||
|  |         "//mediapipe/modules/face_detection:face_detection_short_range.tflite", | ||||||
|  |         "//mediapipe/modules/face_landmark:face_landmark_with_attention.tflite", | ||||||
|  |     ], | ||||||
|  |     deps = [ | ||||||
|  |         "//mediapipe/objc:mediapipe_framework_ios", | ||||||
|  |         "//mediapipe/objc:mediapipe_input_sources_ios", | ||||||
|  |         "//mediapipe/calculators/core:packet_presence_calculator", | ||||||
|  |         "//mediapipe/framework/port:opencv_video", | ||||||
|  |         "//mediapipe/framework/port:opencv_imgcodecs", | ||||||
|  | #        "//mediapipe/objc:mediapipe_layer_renderer", # no need for layer renderer since I don't render | ||||||
|  |     ] + select({ | ||||||
|  |         "//conditions:default": [ | ||||||
|  |             "//mediapipe/graphs/face_mesh:mobile_calculators", | ||||||
|  |             "//mediapipe/framework/formats:landmark_cc_proto", | ||||||
|  |         ], | ||||||
|  |     }), | ||||||
|  | ) | ||||||
							
								
								
									
										58
									
								
								mediapipe/examples/ios/facemeshioslib/FaceMesh.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								mediapipe/examples/ios/facemeshioslib/FaceMesh.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | ||||||
|  | #import <CoreVideo/CoreVideo.h> | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  | 
 | ||||||
|  | @interface IntPoint : NSObject | ||||||
|  | @property(nonatomic) NSInteger x; | ||||||
|  | @property(nonatomic) NSInteger y; | ||||||
|  | 
 | ||||||
|  | - (instancetype)initWithX:(NSInteger)x y:(NSInteger)y; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface NSValue (IntPoint) | ||||||
|  | + (instancetype)valuewithIntPoint:(IntPoint *)value; | ||||||
|  | @property (readonly) IntPoint* intPointValue; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface FaceMeshLandmarkPoint : NSObject | ||||||
|  | @property(nonatomic) float x; | ||||||
|  | @property(nonatomic) float y; | ||||||
|  | @property(nonatomic) float z; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface FaceMeshNormalizedRect : NSObject | ||||||
|  | @property(nonatomic) float centerX; | ||||||
|  | @property(nonatomic) float centerY; | ||||||
|  | @property(nonatomic) float height; | ||||||
|  | @property(nonatomic) float width; | ||||||
|  | @property(nonatomic) float rotation; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @protocol FaceMeshDelegate <NSObject> | ||||||
|  | @optional | ||||||
|  | /**
 | ||||||
|  |  * Array of faces, with faces represented by arrays of face landmarks | ||||||
|  |  */ | ||||||
|  | - (void)didReceiveFaces:(NSArray<NSArray<FaceMeshLandmarkPoint *> *> *)faces; | ||||||
|  | 
 | ||||||
|  | - (void)didSavedRegions:(NSArray<NSURL *> *)foreheadURLs | ||||||
|  |           leftcheekURLs:(NSArray<NSURL *> *)leftcheekURLs | ||||||
|  |           rightcheekURLs:(NSArray<NSURL *> *)rightcheekURLs; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @interface FaceMesh : NSObject | ||||||
|  | - (instancetype)init; | ||||||
|  | - (void)startGraph; | ||||||
|  | - (void)processVideoFrame:(CVPixelBufferRef)imageBuffer; | ||||||
|  | - (CVPixelBufferRef)resize:(CVPixelBufferRef)pixelBuffer | ||||||
|  |                      width:(int)width | ||||||
|  |                     height:(int)height; | ||||||
|  | - (uint8_t **)buffer2Array2D:(CVPixelBufferRef)pixelBuffer; | ||||||
|  | - (void)extractRegions:(NSURL *)fileName | ||||||
|  |             foreheadBoxes:(NSArray<NSArray<IntPoint *> *> *)foreheadBoxes | ||||||
|  |             leftCheekBoxes:(NSArray<NSArray<IntPoint *> *> *)leftCheekBoxes | ||||||
|  |             rightCheekBoxes:(NSArray<NSArray<IntPoint *> *> *)rightCheekBoxes | ||||||
|  |             totalFramesNeedProcess:(NSInteger)totalFramesNeedProcess | ||||||
|  |             skipNFirstFrames:(NSInteger)skipNFirstFrames; | ||||||
|  | @property(weak, nonatomic) id<FaceMeshDelegate> delegate; | ||||||
|  | @property(nonatomic) size_t timestamp; | ||||||
|  | @end | ||||||
							
								
								
									
										397
									
								
								mediapipe/examples/ios/facemeshioslib/FaceMesh.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										397
									
								
								mediapipe/examples/ios/facemeshioslib/FaceMesh.mm
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,397 @@ | ||||||
|  | #import "FaceMesh.h" | ||||||
|  | #import "mediapipe/objc/MPPCameraInputSource.h" | ||||||
|  | #import "mediapipe/objc/MPPGraph.h" | ||||||
|  | 
 | ||||||
|  | #include "mediapipe/framework/formats/landmark.pb.h" | ||||||
|  | #include "mediapipe/framework/formats/rect.pb.h" | ||||||
|  | #include "mediapipe/framework/formats/detection.pb.h" | ||||||
|  | 
 | ||||||
|  | #include "mediapipe/framework/port/opencv_core_inc.h" | ||||||
|  | #include "mediapipe/framework/port/opencv_imgproc_inc.h" | ||||||
|  | #include "mediapipe/framework/port/opencv_video_inc.h" | ||||||
|  | #include "mediapipe/framework/port/opencv_imgcodecs_inc.h" | ||||||
|  | #include "opencv2/imgcodecs/ios.h"  | ||||||
|  | 
 | ||||||
|  | #import <UIKit/UIKit.h> | ||||||
|  | 
 | ||||||
|  | //#import "mediapipe/objc/MPPLayerRenderer.h" | ||||||
|  | 
 | ||||||
|  | // The graph name specified is supposed to be the same as in the pb file (binarypb?) | ||||||
|  | static NSString* const kGraphName = @"face_mesh_ios_lib_gpu"; | ||||||
|  | // static NSString* const kGraphName = @"pure_face_mesh_mobile_gpu"; | ||||||
|  | 
 | ||||||
|  | static const char* kInputStream = "input_video"; | ||||||
|  | static const char* kNumFacesInputSidePacket = "num_faces"; | ||||||
|  | static const char* kLandmarksOutputStream = "multi_face_landmarks"; | ||||||
|  | 
 | ||||||
|  | // Max number of faces to detect/process. | ||||||
|  | static const int kNumFaces = 1; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @interface FaceMesh () <MPPGraphDelegate> | ||||||
|  | @property(nonatomic) MPPGraph* mediapipeGraph; | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @implementation FaceMesh {} | ||||||
|  | 
 | ||||||
|  | #pragma mark - Cleanup methods | ||||||
|  | 
 | ||||||
|  | - (void)dealloc { | ||||||
|  |   self.mediapipeGraph.delegate = nil; | ||||||
|  |   [self.mediapipeGraph cancel]; | ||||||
|  |   // Ignore errors since we're cleaning up. | ||||||
|  |   [self.mediapipeGraph closeAllInputStreamsWithError:nil]; | ||||||
|  |   [self.mediapipeGraph waitUntilDoneWithError:nil]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #pragma mark - MediaPipe graph methods | ||||||
|  | 
 | ||||||
|  | + (MPPGraph*)loadGraphFromResource:(NSString*)resource { | ||||||
|  |   // Load the graph config resource. | ||||||
|  |   NSError* configLoadError = nil; | ||||||
|  |   NSBundle* bundle = [NSBundle bundleForClass:[self class]]; | ||||||
|  |   if (!resource || resource.length == 0) { | ||||||
|  |     return nil; | ||||||
|  |   } | ||||||
|  |   NSURL* graphURL = [bundle URLForResource:resource withExtension:@"binarypb"]; | ||||||
|  |   NSData* data = [NSData dataWithContentsOfURL:graphURL options:0 error:&configLoadError]; | ||||||
|  |   if (!data) { | ||||||
|  |     NSLog(@"Failed to load MediaPipe graph config: %@", configLoadError); | ||||||
|  |     return nil; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Parse the graph config resource into mediapipe::CalculatorGraphConfig proto object. | ||||||
|  |   mediapipe::CalculatorGraphConfig config; | ||||||
|  |   config.ParseFromArray(data.bytes, data.length); | ||||||
|  | 
 | ||||||
|  |   // Create MediaPipe graph with mediapipe::CalculatorGraphConfig proto object. | ||||||
|  |   MPPGraph* newGraph = [[MPPGraph alloc] initWithGraphConfig:config]; | ||||||
|  |    | ||||||
|  |   // Set graph configurations | ||||||
|  |   [newGraph setSidePacket:(mediapipe::MakePacket<int>(kNumFaces)) | ||||||
|  |                               named:kNumFacesInputSidePacket]; | ||||||
|  | 
 | ||||||
|  |   [newGraph addFrameOutputStream:kLandmarksOutputStream | ||||||
|  |                 outputPacketType:MPPPacketTypeRaw]; | ||||||
|  |   return newGraph; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (instancetype)init { | ||||||
|  |   self = [super init]; | ||||||
|  |   if (self) { | ||||||
|  |     self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName]; | ||||||
|  |     self.mediapipeGraph.delegate = self; | ||||||
|  |      | ||||||
|  |     // // Set maxFramesInFlight to a small value to avoid memory contention | ||||||
|  |     // // for real-time processing. | ||||||
|  |     // self.mediapipeGraph.maxFramesInFlight = 2; | ||||||
|  |     NSLog(@"inited graph %@", kGraphName); | ||||||
|  |   } | ||||||
|  |   return self; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)startGraph { | ||||||
|  |   NSError* error; | ||||||
|  |   if (![self.mediapipeGraph startWithError:&error]) { | ||||||
|  |     NSLog(@"Failed to start graph: %@", error); | ||||||
|  |   } | ||||||
|  |   NSLog(@"Started graph %@", kGraphName); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #pragma mark - MPPGraphDelegate methods | ||||||
|  | 
 | ||||||
|  | // Receives CVPixelBufferRef from the MediaPipe graph. Invoked on a MediaPipe worker thread. | ||||||
|  | - (void)mediapipeGraph:(MPPGraph*)graph | ||||||
|  |     didOutputPixelBuffer:(CVPixelBufferRef)pixelBuffer | ||||||
|  |               fromStream:(const std::string&)streamName { | ||||||
|  |   NSLog(@"recv pixelBuffer from %@", @(streamName.c_str())); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Receives a raw packet from the MediaPipe graph. Invoked on a MediaPipe worker thread. | ||||||
|  | - (void)mediapipeGraph:(MPPGraph*)graph | ||||||
|  |      didOutputPacket:(const ::mediapipe::Packet&)packet | ||||||
|  |           fromStream:(const std::string&)streamName { | ||||||
|  |   if (streamName == kLandmarksOutputStream) { | ||||||
|  |     if (packet.IsEmpty()) { // This condition never gets called because FaceLandmarkFrontGpu does not process when there are no detections | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const auto& multi_face_landmarks = packet.Get<std::vector<::mediapipe::NormalizedLandmarkList>>(); | ||||||
|  |     // NSLog(@"[TS:%lld] Number of face instances with landmarks: %lu", packet.Timestamp().Value(), | ||||||
|  |     //       multi_face_landmarks.size()); | ||||||
|  |     NSMutableArray <NSArray <FaceMeshLandmarkPoint *>*>*faceLandmarks = [NSMutableArray new]; | ||||||
|  |      | ||||||
|  |     for (int face_index = 0; face_index < multi_face_landmarks.size(); ++face_index) { | ||||||
|  |       NSMutableArray *thisFaceLandmarks = [NSMutableArray new]; | ||||||
|  |       const auto& landmarks = multi_face_landmarks[face_index]; | ||||||
|  | //      NSLog(@"\tNumber of landmarks for face[%d]: %d", face_index, landmarks.landmark_size()); | ||||||
|  |       for (int i = 0; i < landmarks.landmark_size(); ++i) { | ||||||
|  | //        NSLog(@"\t\tLandmark[%d]: (%f, %f, %f)", i, landmarks.landmark(i).x(), | ||||||
|  | //              landmarks.landmark(i).y(), landmarks.landmark(i).z()); | ||||||
|  |         FaceMeshLandmarkPoint *obj_landmark = [FaceMeshLandmarkPoint new]; | ||||||
|  |         obj_landmark.x = landmarks.landmark(i).x(); | ||||||
|  |         obj_landmark.y = landmarks.landmark(i).y(); | ||||||
|  |         obj_landmark.z = landmarks.landmark(i).z(); | ||||||
|  |         [thisFaceLandmarks addObject:obj_landmark]; | ||||||
|  |       } | ||||||
|  |       [faceLandmarks addObject:thisFaceLandmarks]; | ||||||
|  |     } | ||||||
|  |     if([self.delegate respondsToSelector:@selector(didReceiveFaces:)]) { | ||||||
|  |       [self.delegate didReceiveFaces:faceLandmarks]; | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     NSLog(@"Unknown %@ packet with stream name %s", packet.IsEmpty() ? @"EMPTY" : @"NON-EMPTY",streamName.c_str()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #pragma mark - MPPInputSourceDelegate methods | ||||||
|  | 
 | ||||||
|  | - (void)processVideoFrame:(CVPixelBufferRef)imageBuffer { | ||||||
|  |   const auto ts = | ||||||
|  |       mediapipe::Timestamp(self.timestamp++ * mediapipe::Timestamp::kTimestampUnitsPerSecond); | ||||||
|  |   NSError* err = nil; | ||||||
|  |   // NSLog(@"sending imageBuffer @%@ to %s", @(ts.DebugString().c_str()), kInputStream); | ||||||
|  |   auto sent = [self.mediapipeGraph sendPixelBuffer:imageBuffer | ||||||
|  |                                         intoStream:kInputStream | ||||||
|  |                                         packetType:MPPPacketTypePixelBuffer | ||||||
|  |                                          timestamp:ts | ||||||
|  |                                     allowOverwrite:NO | ||||||
|  |                                              error:&err]; | ||||||
|  |   // NSLog(@"imageBuffer %s", sent ? "sent!" : "not sent."); | ||||||
|  |   if (err) { | ||||||
|  |     NSLog(@"sendPixelBuffer error: %@", err); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | - (void)extractRegions:(NSURL *)fileName | ||||||
|  |             foreheadBoxes:(NSArray<NSArray<IntPoint *> *> *)foreheadBoxes | ||||||
|  |             leftCheekBoxes:(NSArray<NSArray<IntPoint *> *> *)leftCheekBoxes | ||||||
|  |             rightCheekBoxes:(NSArray<NSArray<IntPoint *> *> *)rightCheekBoxes | ||||||
|  |             totalFramesNeedProcess:(NSInteger)totalFramesNeedProcess | ||||||
|  |             skipNFirstFrames:(NSInteger)skipNFirstFrames { | ||||||
|  | 
 | ||||||
|  |     NSString *filename = fileName.path; | ||||||
|  |     std::string filePath = [filename UTF8String]; | ||||||
|  | 
 | ||||||
|  |     cv::VideoCapture vid(filePath); | ||||||
|  | 
 | ||||||
|  |     if (!vid.isOpened()) { | ||||||
|  |         printf("@Error Opening video file"); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         printf("File Opened AAAA"); | ||||||
|  | 
 | ||||||
|  |         // NSMutableArray *foreheadURLs = [NSMutableArray new]; | ||||||
|  |         // NSMutableArray *leftcheekURLs = [NSMutableArray new]; | ||||||
|  |         // NSMutableArray *rightcheekURLs = [NSMutableArray new]; | ||||||
|  | 
 | ||||||
|  |         int startFrame = int(vid.get(cv::CAP_PROP_POS_FRAMES)); | ||||||
|  |         int totalFrame = int(vid.get(cv::CAP_PROP_FRAME_COUNT)); | ||||||
|  |         // int nframes = totalFrame - startFrame; | ||||||
|  | 
 | ||||||
|  |         // if (totalFramesNeedProcess > nframes) { | ||||||
|  |         //   NSLog(@"Video too short"); | ||||||
|  |         //   return; | ||||||
|  |         // } | ||||||
|  | 
 | ||||||
|  |         if (skipNFirstFrames < 0 || totalFramesNeedProcess < 0) { | ||||||
|  |           vid.release(); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         int frameIdx = skipNFirstFrames; | ||||||
|  |         int maxFrameIndex = totalFramesNeedProcess; | ||||||
|  | 
 | ||||||
|  |         if (skipNFirstFrames > 0) { | ||||||
|  |           maxFrameIndex += skipNFirstFrames; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Process forehead | ||||||
|  |         std::vector<cv::Mat> foreheads; | ||||||
|  |         std::vector<cv::Mat> leftcheeks; | ||||||
|  |         std::vector<cv::Mat> rightcheeks; | ||||||
|  |          | ||||||
|  |         while (frameIdx < maxFrameIndex) { | ||||||
|  |           cv::Mat frame; | ||||||
|  | 
 | ||||||
|  |           if (!vid.read(frame)) { | ||||||
|  |               NSLog(@"Failed to read frame."); | ||||||
|  |               break; | ||||||
|  |           } | ||||||
|  |           cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB); | ||||||
|  |           // Process the frame (e.g., display or save it) | ||||||
|  | 
 | ||||||
|  |           NSLog(@"frame index: %d", frameIdx); | ||||||
|  | 
 | ||||||
|  |           NSMutableArray *rowForehead = [foreheadBoxes objectAtIndex:(frameIdx - skipNFirstFrames)]; | ||||||
|  |           NSMutableArray *rowLeftCheek = [leftCheekBoxes objectAtIndex:(frameIdx - skipNFirstFrames)]; | ||||||
|  |           NSMutableArray *rowRightCheek = [rightCheekBoxes objectAtIndex:(frameIdx - skipNFirstFrames)]; | ||||||
|  | 
 | ||||||
|  |           cv::Mat forehead = extractRegion(frame, rowForehead); | ||||||
|  |           cv::Mat leftCheek = extractRegion(frame, rowLeftCheek); | ||||||
|  |           cv::Mat rightCheek = extractRegion(frame, rowRightCheek); | ||||||
|  | 
 | ||||||
|  |           foreheads.push_back(forehead); | ||||||
|  |           leftcheeks.push_back(leftCheek); | ||||||
|  |           rightcheeks.push_back(rightCheek); | ||||||
|  |            | ||||||
|  |           frameIdx++; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         NSLog(@"total foreheads: %d", foreheads.size()); | ||||||
|  |         NSLog(@"total leftCheeks: %d", leftcheeks.size()); | ||||||
|  |         NSLog(@"total rightCheeks: %d", rightcheeks.size()); | ||||||
|  | 
 | ||||||
|  |         // for (int i = 0; i < foreheads.size(); i++) { | ||||||
|  |         //   cv::Mat rgbaImage; | ||||||
|  |         //   cv::cvtColor(foreheads[i], rgbaImage, cv::COLOR_RGB2RGBA); | ||||||
|  |         //   saveCVImageAsPNG(MatToUIImage(rgbaImage), @"forehead"); | ||||||
|  |         // } | ||||||
|  | 
 | ||||||
|  |         NSMutableArray *foreheadURLs = saveCVImagesAsPNGs(foreheads, @"forehead"); | ||||||
|  |         NSMutableArray *leftcheekURLs = saveCVImagesAsPNGs(leftcheeks, @"leftcheek"); | ||||||
|  |         NSMutableArray *rightcheekURLs = saveCVImagesAsPNGs(rightcheeks, @"rightcheek"); | ||||||
|  | 
 | ||||||
|  |         // cv::cvtColor(leftCheeks[0], rgbaImage, cv::COLOR_RGB2RGBA); | ||||||
|  |         // NSData *firstData = [NSData dataWithBytes:rgbaImage.data length:rgbaImage.total() * rgbaImage.elemSize()]; | ||||||
|  |         // saveCVImageAsPNG([UIImage imageWithData:firstData], @"leftcheek"); | ||||||
|  | 
 | ||||||
|  |         // cv::cvtColor(rightCheeks[0], rgbaImage, cv::COLOR_RGB2RGBA); | ||||||
|  |         // NSData *firstData = [NSData dataWithBytes:rgbaImage.data length:rgbaImage.total() * rgbaImage.elemSize()]; | ||||||
|  |         // saveCVImageAsPNG([UIImage imageWithData:firstData], @"rightcheek"); | ||||||
|  | 
 | ||||||
|  |         if([self.delegate respondsToSelector:@selector(didSavedRegions:leftcheekURLs:rightcheekURLs:)]) { | ||||||
|  |             NSLog(@"nguyencse ==> has didSavedRegions"); | ||||||
|  |             [self.delegate didSavedRegions:foreheadURLs leftcheekURLs:leftcheekURLs rightcheekURLs:rightcheekURLs]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     vid.release(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | cv::Mat extractRegion(cv::Mat img, NSArray<IntPoint *> *box) { | ||||||
|  |   IntPoint* point0 = [box objectAtIndex:0]; | ||||||
|  |   IntPoint* point1 = [box objectAtIndex:1]; | ||||||
|  |   IntPoint* point2 = [box objectAtIndex:2]; | ||||||
|  |   IntPoint* point3 = [box objectAtIndex:3]; | ||||||
|  | 
 | ||||||
|  |   // LEFT TOP --> RIGHT TOP --> RIGHT BOTTOM --> LEFT BOTTOM | ||||||
|  |   int frameWidth = point1.x - point0.x; | ||||||
|  |   int frameHeight = point3.y - point0.y; | ||||||
|  |   cv::Mat region = cropROI(img, cv::Rect(point0.x, point0.y, frameWidth, frameHeight)); | ||||||
|  | 
 | ||||||
|  |   // square | ||||||
|  |   region = square(region); | ||||||
|  | 
 | ||||||
|  |   // resize to 32x32 | ||||||
|  |   region = resizeWithAreaInterpolation(region, cv::Size(32, 32)); | ||||||
|  | 
 | ||||||
|  |   return region; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | cv::Mat cropROI(cv::Mat src, cv::Rect roi) { | ||||||
|  |   // Crop the full image to that image contained by the rectangle myROI | ||||||
|  |   // Note that this doesn't copy the data | ||||||
|  |   cv::Mat croppedRef(src, roi); | ||||||
|  | 
 | ||||||
|  |   cv::Mat cropped; | ||||||
|  |   // Copy the data into new matrix | ||||||
|  |   croppedRef.copyTo(cropped); | ||||||
|  | 
 | ||||||
|  |   return cropped; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | cv::Mat square(cv::Mat frame) { | ||||||
|  |     if (frame.rows < frame.cols) { | ||||||
|  |         int diff = frame.cols - frame.rows; | ||||||
|  |         cv::Mat pad(frame.rows, diff, frame.type(), cv::Scalar(0)); | ||||||
|  |         cv::hconcat(frame, pad, frame); | ||||||
|  |     } else if (frame.rows > frame.cols) { | ||||||
|  |         int diff = frame.rows - frame.cols; | ||||||
|  |         cv::Mat pad(diff, frame.cols, frame.type(), cv::Scalar(0)); | ||||||
|  |         cv::vconcat(frame, pad, frame); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return frame; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | cv::Mat resizeWithAreaInterpolation(cv::Mat image, cv::Size newShape) { | ||||||
|  |     cv::Size originalShape = image.size(); | ||||||
|  |     cv::resize(image, image, newShape, 0, 0, cv::INTER_AREA); | ||||||
|  |     return image; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NSURL *saveCVImageAsPNG(UIImage *image, NSString *fileName) { | ||||||
|  | //     // Create a unique file name for each image | ||||||
|  | //     NSString *fileNameIndex = [fileName stringByAppendingFormat:@"_%d", 0]; | ||||||
|  | //     NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[fileNameIndex stringByAppendingString:@".png"]]; | ||||||
|  | //     NSURL *fileURL = [NSURL fileURLWithPath:filePath]; | ||||||
|  | 
 | ||||||
|  | //     NSData *pngData = UIImagePNGRepresentation(image); | ||||||
|  | 
 | ||||||
|  | //     if ([pngData writeToFile:filePath atomically:YES]) { | ||||||
|  | //         NSLog(@"PNG file saved successfully at path: %@", filePath); | ||||||
|  | //     } else { | ||||||
|  | //         NSLog(@"Failed to save PNG file at path: %@", filePath); | ||||||
|  | //     } | ||||||
|  | 
 | ||||||
|  | //     return fileURL; | ||||||
|  | // } | ||||||
|  | 
 | ||||||
|  | NSArray<NSURL *> *saveCVImagesAsPNGs(std::vector<cv::Mat> frames, NSString *folderName) { | ||||||
|  |     NSMutableArray<NSURL *> *fileURLs = [NSMutableArray arrayWithCapacity:frames.size()]; | ||||||
|  | 
 | ||||||
|  |     for (int i = 0; i < frames.size(); i++) { | ||||||
|  |           // Create a unique file name for each image | ||||||
|  |         NSString *fileNameIndex = [folderName stringByAppendingFormat:@"_%d", i]; | ||||||
|  |         NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[fileNameIndex stringByAppendingString:@".png"]]; | ||||||
|  |         NSURL *fileURL = [NSURL fileURLWithPath:filePath]; | ||||||
|  | 
 | ||||||
|  |         // NSLog(@"File URL: %@", fileURL); | ||||||
|  | 
 | ||||||
|  |         // Do NOT compress pixel | ||||||
|  |         std::vector<int> compressionParams; | ||||||
|  |         compressionParams.push_back(cv::IMWRITE_PNG_COMPRESSION); | ||||||
|  |         compressionParams.push_back(0); | ||||||
|  | 
 | ||||||
|  |         const char * cpath = [filePath cStringUsingEncoding:NSUTF8StringEncoding]; | ||||||
|  |         const cv::String newPath = (const cv::String)cpath; | ||||||
|  |         cv::imwrite(newPath, frames[i], compressionParams); | ||||||
|  | 
 | ||||||
|  |         // Add file URL to the array | ||||||
|  |         [fileURLs addObject:fileURL]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return [fileURLs copy]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  |   | ||||||
|  | @implementation NSValue (IntPoint) | ||||||
|  | + (instancetype)valuewithIntPoint:(IntPoint *)value { | ||||||
|  |     return [self valueWithBytes:&value objCType:@encode(IntPoint)]; | ||||||
|  | } | ||||||
|  | - (IntPoint *) intPointValue { | ||||||
|  |     IntPoint* value; | ||||||
|  |     [self getValue:&value]; | ||||||
|  |     return value; | ||||||
|  | } | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @implementation FaceMeshLandmarkPoint | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @implementation FaceMeshNormalizedRect | ||||||
|  | @end | ||||||
|  | 
 | ||||||
|  | @implementation IntPoint | ||||||
|  | - (instancetype)initWithX:(NSInteger)x y:(NSInteger)y { | ||||||
|  |     self = [super init]; | ||||||
|  |     if (self) { | ||||||
|  |         _x = x; | ||||||
|  |         _y = y; | ||||||
|  |     } | ||||||
|  |     return self; | ||||||
|  | } | ||||||
|  | @end | ||||||
							
								
								
									
										60
									
								
								mediapipe/examples/ios/facemeshioslib/patch_ios_framework.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								mediapipe/examples/ios/facemeshioslib/patch_ios_framework.sh
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | #!/bin/bash | ||||||
|  | set -eu | ||||||
|  | set -o pipefail | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | [[ $# -lt 2 ]] && echo "Usage: $0  <path/to/zipped .framework> <hdrs>..." && exit 1 | ||||||
|  | zipped=$(python3 -c "import os; print(os.path.realpath('$1'))"); shift | ||||||
|  | name=$(basename "$zipped" .zip) | ||||||
|  | parent=$(dirname "$zipped") | ||||||
|  | named="$parent"/"$name".framework | ||||||
|  | 
 | ||||||
|  | unzip "$zipped" -d "$parent" | ||||||
|  | 
 | ||||||
|  | mkdir "$named"/Modules | ||||||
|  | cat << EOF >"$named"/Modules/module.modulemap | ||||||
|  | framework module $name { | ||||||
|  |   umbrella header "$name.h" | ||||||
|  | 
 | ||||||
|  |   export * | ||||||
|  |   module * { export * } | ||||||
|  | 
 | ||||||
|  |   link framework "AVFoundation" | ||||||
|  |   link framework "Accelerate" | ||||||
|  |   link framework "AssetsLibrary" | ||||||
|  |   link framework "CoreFoundation" | ||||||
|  |   link framework "CoreGraphics" | ||||||
|  |   link framework "CoreImage" | ||||||
|  |   link framework "CoreMedia" | ||||||
|  |   link framework "CoreVideo" | ||||||
|  |   link framework "GLKit" | ||||||
|  |   link framework "Metal" | ||||||
|  |   link framework "MetalKit" | ||||||
|  |   link framework "OpenGLES" | ||||||
|  |   link framework "QuartzCore" | ||||||
|  |   link framework "UIKit" | ||||||
|  | } | ||||||
|  | EOF | ||||||
|  | # NOTE: All these linked frameworks are required by mediapipe/objc. | ||||||
|  | 
 | ||||||
|  | cat << EOF >"$named"/Headers/$name.h | ||||||
|  | // | ||||||
|  | //  $name.h | ||||||
|  | //  $name | ||||||
|  | // | ||||||
|  | 
 | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  | 
 | ||||||
|  | //! Project version number for $name. | ||||||
|  | FOUNDATION_EXPORT double ${name}VersionNumber; | ||||||
|  | 
 | ||||||
|  | //! Project version string for $name. | ||||||
|  | FOUNDATION_EXPORT const unsigned char ${name}VersionString[]; | ||||||
|  | 
 | ||||||
|  | // In this header, you should import all the public headers of your framework using statements like #import <$name/PublicHeader.h> | ||||||
|  | 
 | ||||||
|  | EOF | ||||||
|  | until [[ $# -eq 0 ]]; do | ||||||
|  |   printf '#import "'"$1"'"\n' "$1" >>"$named"/Headers/$name.h | ||||||
|  |   shift | ||||||
|  | done | ||||||
							
								
								
									
										
											BIN
										
									
								
								mediapipe/graphs/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mediapipe/graphs/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								mediapipe/graphs/face_mesh/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mediapipe/graphs/face_mesh/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -67,3 +67,17 @@ mediapipe_binary_graph( | ||||||
|     output_name = "face_mesh_mobile_gpu.binarypb", |     output_name = "face_mesh_mobile_gpu.binarypb", | ||||||
|     deps = [":mobile_calculators"], |     deps = [":mobile_calculators"], | ||||||
| ) | ) | ||||||
|  | 
 | ||||||
|  | mediapipe_binary_graph( | ||||||
|  |     name = "face_mesh_ios_lib_gpu_binary_graph", | ||||||
|  |     graph = "face_mesh_ios_lib.pbtxt", | ||||||
|  |     output_name = "face_mesh_ios_lib_gpu.binarypb", | ||||||
|  |     deps = [":mobile_calculators"], | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | mediapipe_binary_graph( | ||||||
|  |     name = "pure_face_mesh_mobile_gpu_binary_graph", | ||||||
|  |     graph = "pure_face_mesh_mobile.pbtxt", | ||||||
|  |     output_name = "pure_face_mesh_mobile_gpu.binarypb", | ||||||
|  |     deps = [":mobile_calculators"], | ||||||
|  | ) | ||||||
|  |  | ||||||
							
								
								
									
										70
									
								
								mediapipe/graphs/face_mesh/face_mesh_ios_lib.pbtxt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								mediapipe/graphs/face_mesh/face_mesh_ios_lib.pbtxt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | ||||||
|  | # MediaPipe graph that performs face mesh with TensorFlow Lite on GPU. | ||||||
|  | 
 | ||||||
|  | # GPU buffer. (GpuBuffer) | ||||||
|  | input_stream: "input_video" | ||||||
|  | 
 | ||||||
|  | # Max number of faces to detect/process. (int) | ||||||
|  | input_side_packet: "num_faces" | ||||||
|  | 
 | ||||||
|  | # Output image with rendered results. (GpuBuffer) | ||||||
|  | # nope no rendering | ||||||
|  | # output_stream: "output_video" | ||||||
|  | # Collection of detected/processed faces, each represented as a list of | ||||||
|  | # landmarks. (std::vector<NormalizedLandmarkList>) | ||||||
|  | output_stream: "multi_face_landmarks" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Throttles the images flowing downstream for flow control. It passes through | ||||||
|  | # the very first incoming image unaltered, and waits for downstream nodes | ||||||
|  | # (calculators and subgraphs) in the graph to finish their tasks before it | ||||||
|  | # passes through another image. All images that come in while waiting are | ||||||
|  | # dropped, limiting the number of in-flight images in most part of the graph to | ||||||
|  | # 1. This prevents the downstream nodes from queuing up incoming images and data | ||||||
|  | # excessively, which leads to increased latency and memory usage, unwanted in | ||||||
|  | # real-time mobile applications. It also eliminates unnecessarily computation, | ||||||
|  | # e.g., the output produced by a node may get dropped downstream if the | ||||||
|  | # subsequent nodes are still busy processing previous inputs. | ||||||
|  | # node { | ||||||
|  | #  calculator: "FlowLimiterCalculator" | ||||||
|  | # input_stream: "input_video" | ||||||
|  | #  input_stream: "FINISHED:output_video" | ||||||
|  | #  input_stream_info: { | ||||||
|  | #    tag_index: "FINISHED" | ||||||
|  | #    back_edge: true | ||||||
|  | #  } | ||||||
|  | #  output_stream: "throttled_input_video" | ||||||
|  | #} | ||||||
|  | 
 | ||||||
|  | # Defines side packets for further use in the graph. | ||||||
|  | node { | ||||||
|  |   calculator: "ConstantSidePacketCalculator" | ||||||
|  |   output_side_packet: "PACKET:with_attention" | ||||||
|  |   node_options: { | ||||||
|  |     [type.googleapis.com/mediapipe.ConstantSidePacketCalculatorOptions]: { | ||||||
|  |       packet { bool_value: true } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Subgraph that detects faces and corresponding landmarks. | ||||||
|  | node { | ||||||
|  |   calculator: "FaceLandmarkFrontGpu" | ||||||
|  |   # input_stream: "IMAGE:throttled_input_video" | ||||||
|  |   input_stream: "IMAGE:input_video" | ||||||
|  |   input_side_packet: "NUM_FACES:num_faces" | ||||||
|  |   input_side_packet: "WITH_ATTENTION:with_attention" | ||||||
|  |   output_stream: "LANDMARKS:multi_face_landmarks" | ||||||
|  |   output_stream: "ROIS_FROM_LANDMARKS:face_rects_from_landmarks" | ||||||
|  |   # output_stream: "DETECTIONS:face_detections" | ||||||
|  |   # output_stream: "ROIS_FROM_DETECTIONS:face_rects_from_detections" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Subgraph that renders face-landmark annotation onto the input image. | ||||||
|  | # node { | ||||||
|  | #  calculator: "FaceRendererGpu" | ||||||
|  | #  input_stream: "IMAGE:throttled_input_video" | ||||||
|  | #  input_stream: "LANDMARKS:multi_face_landmarks" | ||||||
|  | #  input_stream: "NORM_RECTS:face_rects_from_landmarks" | ||||||
|  | #  input_stream: "DETECTIONS:face_detections" | ||||||
|  | #  output_stream: "IMAGE:output_video" | ||||||
|  | #} | ||||||
							
								
								
									
										74
									
								
								mediapipe/graphs/face_mesh/pure_face_mesh_mobile.pbtxt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								mediapipe/graphs/face_mesh/pure_face_mesh_mobile.pbtxt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | ||||||
|  | # MediaPipe graph that performs face mesh with TensorFlow Lite on GPU. | ||||||
|  | # Edited from face_mesh_mobile.pbtxt because I don't want babysitting of auto throttling and such. I'll do it myself | ||||||
|  | 
 | ||||||
|  | # GPU buffer. (GpuBuffer) | ||||||
|  | input_stream: "input_video" | ||||||
|  | 
 | ||||||
|  | # Max number of faces to detect/process. (int) | ||||||
|  | input_side_packet: "num_faces" | ||||||
|  | 
 | ||||||
|  | # Output image with rendered results. (GpuBuffer) | ||||||
|  | # nope no rendering | ||||||
|  | # output_stream: "output_video" | ||||||
|  | 
 | ||||||
|  | # Collection of detected/processed faces, each represented as a list of | ||||||
|  | # landmarks. (std::vector<NormalizedLandmarkList>) | ||||||
|  | output_stream: "multi_face_landmarks" | ||||||
|  | 
 | ||||||
|  | # Regions of interest calculated based on landmarks. | ||||||
|  | # (For more info see mediapipe/modules/face_landmark/face_landmark_front_gpu.pbtxt) | ||||||
|  | # (std::vector<NormalizedRect>) | ||||||
|  | # For typings see "mediapipe/framework/formats/rect.pb.h" | ||||||
|  | output_stream: "face_rects_from_landmarks" | ||||||
|  | 
 | ||||||
|  | # The detections from the box model | ||||||
|  | # see detection.proto | ||||||
|  | # Regions of interest calculated based on face detections. | ||||||
|  | # (std::vector<NormalizedRect>) | ||||||
|  | # output_stream: "face_rects_from_detections" | ||||||
|  | 
 | ||||||
|  | # Extra outputs (for debugging, for instance). | ||||||
|  | # Detected faces. (std::vector<Detection>) | ||||||
|  | # (std::vector<Detections>) | ||||||
|  | # output_stream: "face_detections" | ||||||
|  | 
 | ||||||
|  | # Landmark presence (needed because whole graph won't emit anything if no faces are detected) | ||||||
|  | output_stream: "landmark_presence" | ||||||
|  | 
 | ||||||
|  | # screw the throttling, we do that ourselves. | ||||||
|  | # *throttling node code was deleted from here* | ||||||
|  | 
 | ||||||
|  | # Subgraph that detects faces and corresponding landmarks. | ||||||
|  | node { | ||||||
|  |   calculator: "FaceLandmarkFrontGpu" | ||||||
|  | # the IMAGE: part is saying, pipe this data into the input with the name `image` | ||||||
|  |   input_stream: "IMAGE:input_video" | ||||||
|  |   input_side_packet: "NUM_FACES:num_faces" | ||||||
|  |   output_stream: "LANDMARKS:multi_face_landmarks" | ||||||
|  |   output_stream: "ROIS_FROM_LANDMARKS:face_rects_from_landmarks" | ||||||
|  |   # face_detections is the stream that comes out from face_detection_short_range_common | ||||||
|  |   # output_stream: "DETECTIONS:face_detections" | ||||||
|  |    | ||||||
|  |   # output_stream: "ROIS_FROM_DETECTIONS:face_rects_from_detections" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # See this thread here https://github.com/google/mediapipe/issues/850#issuecomment-683268033 | ||||||
|  | # "if there are no packets in the corresponding output stream, it is designed to wait until the packet comes in" | ||||||
|  | # That means that we'd get absolutely nothing to work with and won't know if our frame had anythin! | ||||||
|  | # So we add PacketPresenceCalculator | ||||||
|  | node { | ||||||
|  |   calculator: "PacketPresenceCalculator" | ||||||
|  |   input_stream: "PACKET:multi_face_landmarks" | ||||||
|  |   output_stream: "PRESENCE:landmark_presence" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # nope not rendering. | ||||||
|  | # Subgraph that renders face-landmark annotation onto the input image. | ||||||
|  | # node { | ||||||
|  | #  calculator: "FaceRendererGpu" | ||||||
|  | #  input_stream: "IMAGE:throttled_input_video" | ||||||
|  | #  input_stream: "LANDMARKS:multi_face_landmarks" | ||||||
|  | #  input_stream: "NORM_RECTS:face_rects_from_landmarks" | ||||||
|  | #  input_stream: "DETECTIONS:face_detections" | ||||||
|  | #  output_stream: "IMAGE:output_video" | ||||||
|  | #} | ||||||
							
								
								
									
										
											BIN
										
									
								
								third_party/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								third_party/.DS_Store
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user