ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Android边缘AI猫脸识别:轻量模型+IoT协同落地实践

Android边缘AI猫脸识别:轻量模型+IoT协同落地实践 1. 项目概述这不是一个“猫脸识别App”而是一套面向边缘智能终端的轻量化视觉感知方案“Android IoT开发之猫脸识别”这个标题乍看像极了某个校园课设或Demo演示——用手机摄像头拍张猫的照片调个OpenCV或者TensorFlow Lite模型弹个框说“识别成功”。但如果你真这么理解就完全错过了它背后真正值得深挖的技术纵深和产业逻辑。我做Android底层适配和IoT边缘计算项目十年从高通820平台刷机开始到带团队交付过37款商用AIoT终端见过太多人把“能跑通”当成“能落地”。而这个项目本质是在资源受限的Android嵌入式设备上构建一套低延迟、低功耗、可离线、可联动的动物行为感知闭环系统。核心关键词不是“识别”而是“Android IoT 猫脸”三者的咬合点Android提供成熟UI与传感器调度能力IoT定义设备联网、状态同步与远程控制协议猫脸识别则是垂直场景下的轻量级AI推理任务。它不追求99.9%的学术精度而要解决“家里三只猫混养时喂食器能否在0.8秒内准确区分‘大橘’并只打开它的食槽”这种真实问题。适用人群非常明确一是想从传统Android App开发转向边缘AI方向的工程师二是正在选型宠物智能硬件的创业团队三是高校物联网课程中需要真实项目案例的教师。它不教你怎么调TensorFlow官网Demo而是手把手告诉你为什么必须把YOLOv5s模型剪枝到2.3MB、为什么FileProvider路径要避开腾讯企业微信的content URI冲突、为什么在MTK8765B平台上CameraX预览帧率会掉到12fps且必须手动切回Legacy API——这些才是你翻遍Stack Overflow也找不到的硬核经验。2. 整体架构设计与技术选型逻辑为什么放弃“标准答案”选择一条更难但更稳的路2.1 架构分层从“能识别”到“可部署”的四层跃迁很多初学者一上来就想堆模型结果在RK3399开发板上跑个ResNet50内存直接爆掉设备烫得不敢摸。我们最终采用的四层架构是踩过至少11块不同SoC高通、联发科、瑞芯微、全志后沉淀下来的方案感知层Perception Layer不直接用Camera2 API而是通过CameraX的ImageAnalysis用例获取YUV_420_888格式帧。关键点在于强制设置targetResolution为640x480非默认全分辨率并启用setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)。实测下来这能让低端设备如Allwinner H616的帧处理吞吐量从3.2fps提升到8.7fps且避免OOM。这里没有玄学只有对Android Camera HAL层缓冲区机制的理解——KeepLatest策略让系统自动丢弃未处理完的旧帧而不是堆积等待这是应对低算力设备的刚需。推理层Inference Layer坚决不用TensorFlow Lite官方提供的“猫狗分类”预训练模型。原因很现实它输出的是“猫/狗”二分类概率而实际需求是“识别具体哪只猫”。我们采用自研的轻量级ArcFace变体输入尺寸固定为112x112Embedding维度压缩至64维原版512维模型体积仅1.8MB。训练数据不是网上爬的公开猫图而是用手机在不同光照、角度下给自家三只猫各拍200张正脸照再用MTCNN做粗定位仿射变换归一化。这个细节决定了落地效果公开模型在侧脸、遮挡场景下误识率超40%而我们的定制模型在真实家庭环境中误识率稳定在6.3%以内。IoT协同层IoT Coordination Layer这是区别于普通Android App的核心。识别结果不只显示在屏幕上而是通过MQTT协议实时推送到本地MQTT BrokerEclipse Mosquitto运行在同局域网的树莓派4B上。Topic设计遵循pet/face/{cat_id}/status规范Payload为JSON{timestamp:1715234567,confidence:0.92,action:feed_open}。重点来了我们没用Android官方的WorkManager做后台保活而是用前台Service Notification ChannelAndroid 8.0强制要求维持长连接。实测发现某品牌安卓电视盒子Amlogic S905X3在待机状态下WorkManager触发延迟高达92秒而前台ServiceMQTT KeepAlive30s的组合平均延迟压到1.7秒。这不是参数调优而是对Android电源管理策略的妥协式适配。应用层Application LayerUI极度克制。主界面只有三个元素实时预览View、识别结果标签绿色字体显示猫名置信度、手动触发按钮。所有网络请求、模型加载、传感器初始化都放在Application子类中完成避免Activity重建导致的重复初始化。特别处理了Android 12的隐私沙盒机制在AndroidManifest.xml中声明uses-permission android:nameandroid.permission.POST_NOTIFICATIONS /并在首次启动时用ActivityCompat.requestPermissions()动态申请否则Notification Channel创建失败前台Service直接崩溃。2.2 关键技术选型背后的“血泪史”为什么不选MediaPipe官方文档吹得天花乱坠但实测在骁龙625平台常见于中低端IoT设备上FaceDetectionGPU的初始化耗时达4.3秒且频繁出现GL_INVALID_OPERATION错误。我们曾为这个问题在Google Issue Tracker上提交了7次复现步骤得到的回复永远是“请升级到最新版”。最后换回纯CPU推理用NCNN框架重写前处理初始化时间压到0.8秒帧率反而从11fps提升到14fps。教训很痛在IoT领域“新”不等于“好”稳定压倒一切。为什么坚持用MQTT而非HTTP轮询某客户曾要求改成HTTP POST到他们云平台理由是“已有API”。我们做了对比测试在200ms网络延迟下MQTT QoS1消息端到端延迟均值为210ms而HTTP POST含DNS解析、TCP握手、TLS协商均值为890ms。更致命的是HTTP轮询每5秒一次设备功耗比MQTT常连高37%。当客户看到他们电池供电的喂食器续航从30天暴跌到9天时立刻改口说“还是MQTT香”。FileProvider路径冲突的根源与解法标题里提到的content://com.tencent.wework.fileprovider/external_path/...这类URI是腾讯系App企业微信、QQ为规避Android 7.0的StrictMode限制而自定义的Provider。当你的App也声明了同名authority如com.example.fileprovider系统会因Provider冲突直接Crash。解决方案不是改自己而是绕开在res/xml/file_paths.xml中将external-path的name属性改为唯一值例如my_cat_recognition_external并在AndroidManifest.xml中对应修改android:authoritiescom.example.catrecog.fileprovider。这个细节在官方文档里根本找不到却是无数IoT设备厂商踩过的坑。3. 核心模块实现详解从模型训练到设备烧录的完整链路3.1 猫脸数据集构建与模型轻量化精度与体积的钢丝绳所谓“猫脸识别”难点从来不在算法本身而在数据。网上能搜到的“Cat Face Dataset”基本是科研机构发布的图片质量参差不齐且标注极其粗糙——只标出“有猫脸”不标“哪只猫”。这导致模型学不会个体差异。我们的做法是回归原始用一台小米12S Ultra在晨、午、昏三个时段对三只猫编号C1/C2/C3各采集200张正面照严格遵循背景统一为浅灰色绒布减少背景干扰光源用两盏5500K色温LED灯呈45度角打光消除眼窝阴影拍摄距离固定为0.8米保证脸部像素占比稳定数据清洗阶段用OpenCV的cv2.CascadeClassifier做初筛剔除模糊、严重侧脸、闭眼样本最终保留有效图像527张。关键一步是人脸对齐不用Dlib太重改用基于68点的轻量级Landmark模型仅127KB提取左右眼中心点计算旋转角度再用cv2.getAffineTransform做仿射变换裁剪出112x112标准脸。整个流程封装成Python脚本一行命令搞定python align_faces.py --input_dir ./raw_cats --output_dir ./aligned_cats --landmark_model ./lite_landmark.tflite。模型训练放弃PyTorch选用TensorFlow 2.12兼容性最好。主干网络用MobileNetV3-Small但关键改动在Head部分去掉最后的GlobalAveragePooling接一个128维的全连接层再用L2归一化最后接64维Embedding层。损失函数用Triplet LossMargin设为0.3。训练时Batch Size设为32显存友好学习率从0.001线性衰减到0.0001。重点来了模型导出不是简单model.save()而是用TF Lite Converter的representative_dataset进行量化。我们准备了一个包含50张校准图像的Dataset执行converter tf.lite.TFLiteConverter.from_saved_model(saved_model) converter.optimizations [tf.lite.Optimize.DEFAULT] converter.representative_dataset representative_dataset_gen converter.target_spec.supported_ops [ tf.lite.OpsSet.TFLITE_BUILTINS_INT8, tf.lite.OpsSet.SELECT_TF_OPS ] converter.inference_input_type tf.int8 converter.inference_output_type tf.int8 tflite_quant_model converter.convert()量化后模型体积从8.2MB锐减至1.8MB推理速度提升2.3倍且精度损失仅0.8%Top-1 Acc从92.4%降至91.6%。这个数字背后是无数次尝试试过FP16量化精度掉到87%试过全整型量化模型直接不收敛。最终选定INT8是精度、速度、体积三者博弈后的最优解。3.2 Android端推理引擎集成绕过NDK编译地狱的务实方案很多教程教你用NDK编译NCNN或MNN听起来很硬核但实际落地时光是配置Application.mk和Android.mk就能耗掉两天。我们选择更直接的路用Android Studio自带的CMake直接集成预编译的ARM64-v8a静态库。步骤如下下载NCNN官方预编译包ncnn-20230515-android-lib.zip解压后找到arm64-v8a/libncnn.a和include/目录。在Android Studio项目中新建src/main/cpp目录将libncnn.a放入src/main/cpp/libs/arm64-v8a/include/整个复制到src/main/cpp/include/。编写CMakeLists.txtcmake_minimum_required(VERSION 3.10.2) project(catrecog) # 添加ncnn库 add_library(ncnn STATIC IMPORTED) set_target_properties(ncnn PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/cpp/libs/${ANDROID_ABI}/libncnn.a) # 创建自己的推理库 add_library(catrecog SHARED catrecog_jni.cpp face_detector.cpp face_aligner.cpp) # 链接ncnn target_link_libraries(catrecog ncnn log android OpenSLES)关键的JNI桥接代码catrecog_jni.cpp中不直接暴露C对象而是用jobject传递Bitmapextern C JNIEXPORT jobject JNICALL Java_com_example_catrecog_FaceEngine_detectCatFace( JNIEnv *env, jobject thiz, jobject bitmap) { // 将Bitmap转为cv::Mat注意ARGB_8888格式 AndroidBitmapInfo info; void *pixels; AndroidBitmap_getInfo(env, bitmap, info); AndroidBitmap_lockPixels(env, bitmap, pixels); cv::Mat src(info.height, info.width, CV_8UC4, pixels); cv::Mat bgr; cv::cvtColor(src, bgr, cv::COLOR_RGBA2BGR); // RGBA转BGR AndroidBitmap_unlockPixels(env, bitmap); // 调用检测器 std::vectorFaceObject faces; detector-detect(bgr, faces); // 构建返回结果 jclass resultClass env-FindClass(com/example/catrecog/FaceResult); jmethodID ctor env-GetMethodID(resultClass, init, (IIF)V); jobject result env-NewObject(resultClass, ctor, faces.size(), faces.empty() ? -1 : faces[0].x, faces.empty() ? 0.0f : faces[0].prob); return result; }这个设计规避了所有JNI类型转换的坑。实测在骁龙439设备上单帧推理耗时稳定在320ms±15ms完全满足实时性要求。而如果走NDK全流程编译光是解决libstdc.so版本冲突就可能卡住一周。3.3 IoT协议栈实现让识别结果真正驱动物理世界识别出猫只是开始让结果产生价值才是IoT的灵魂。我们采用分层协议设计物理层设备通过ESP32-WROVER模组接入Wi-Fi该模组内置TCP/IP协议栈降低主控CPU负担。传输层MQTT over TLS 1.2证书预置在设备Flash中避免每次连接都下载CA证书。应用层自定义Topic结构如前所述pet/face/{cat_id}/status但Payload增加device_id字段便于多设备集群管理。Android端MQTT客户端选用Paho Android Clientorg.eclipse.paho:org.eclipse.paho.android.service:1.1.1而非更流行的MQTTAndroidClient。原因后者在Android 12上存在后台服务被杀问题而Paho通过绑定系统Service稳定性更高。初始化代码关键点// 创建MQTT连接选项 MqttConnectOptions options new MqttConnectOptions(); options.setUserName(catiot); options.setPassword(secure_pass.toCharArray()); options.setConnectionTimeout(30); options.setKeepAliveInterval(30); // 心跳30秒平衡功耗与可靠性 options.setCleanSession(true); // 连接 client new MqttAndroidClient(this, tcp://192.168.1.100:1883, cat_ Build.SERIAL); client.setCallback(new MqttCallbackExtended() { Override public void connectComplete(boolean reconnect, String serverURI) { // 连接成功后订阅主题 try { client.subscribe(pet/control/#, 1); // 接收控制指令 } catch (MqttException e) { Log.e(MQTT, Subscribe failed, e); } } // ... 其他回调方法 }); client.connect(options, null, new IMqttActionListener() { Override public void onSuccess(IMqttToken asyncActionToken) { Log.d(MQTT, Connected); } Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { Log.e(MQTT, Connect failed, exception); } });最精妙的设计在“状态同步”环节。当识别出C1猫时不仅推送pet/face/C1/status还同时向pet/device/feeder/status推送{cat_id:C1,timestamp:1715234567,state:opening}。喂食器固件监听此Topic收到后执行电机动作。这样Android设备只负责“感知决策”执行交给专用硬件符合IoT分层解耦原则。4. 实操避坑指南那些文档不会写的“现场事故”与救火技巧4.1 CameraX预览黑屏的七种死法与解法CameraX是Google主推但落地时黑屏率高达65%。我们整理了真实产线中遇到的七种典型场景及根治方案场景描述根本原因解决方案验证方式预览启动后瞬间黑屏Logcat无报错设备厂商在Camera HAL中禁用了YUV输出格式在Preview.Builder中显式设置setTargetFormat(ImageFormat.YUV_420_888)并捕获IllegalStateException抓取adb logcat -s CameraCaptureSession搜索Invalid output format预览正常但ImageAnalysis回调never触发ImageAnalysis.setBackpressureStrategy()未设置缓冲区满溢必须调用setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)在analyze()方法第一行加Log.d(ANALYZE, Frame received)确认日志是否打印预览画面严重偏色整体发绿设备未正确实现android.info.supportedHardwareLevel导致CameraX误判为LEGACY设备强制指定CameraSelector.DEFAULT_BACK_CAMERA并用CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL验证cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL预览帧率忽高忽低15fps→3fps→15fps循环系统电源管理策略动态降频CPU在Application.onCreate()中调用PowerManager.WakeLock保持CPU唤醒powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, CatRecog:CPU)横屏预览时画面拉伸变形PreviewView的scaleType未设为FILL_STARTXML中设置app:scaleTypefillStart代码中previewView.setScaleType(PreviewView.ScaleType.FILL_START)对比previewView.getDisplay().getRotation()与cameraInfo.getSensorRotation()预览窗口闪烁1秒闪一次PreviewView与TextureView混用Surface生命周期冲突统一使用PreviewView禁用所有TextureView相关代码删除项目中所有TextureView的import和实例化代码预览画面有固定位置噪点如右上角白点CMOS传感器坏点需硬件校准联系模组厂提供OTPOne-Time Programmable校准数据烧录到EEPROM提供adb shell dumpsys media.camera输出给供应商分析提示所有CameraX问题第一步永远是adb logcat -s CameraX第二步是adb shell dumpsys media.camera。别急着改代码先看系统层到底发生了什么。4.2 模型推理性能断崖式下跌的三大元凶在RK3326开发板上我们曾遇到模型推理从320ms骤增至1200ms的诡异现象。排查过程堪称教科书级元凶一内存碎片化低端设备RAM仅512MB频繁new Mat()导致内存碎片。解决方案预分配内存池。在FaceDetector类中声明static Mat mRgb; static Mat mResized;在initialize()中一次性mRgb new Mat(480, 640, CvType.CV_8UC3);。实测内存占用下降42%推理时间稳定在310ms。元凶二线程抢占Android系统后台有大量JobScheduler任务与推理线程争抢CPU。解决方案将推理线程设为THREAD_PRIORITY_AUDIO而非默认的THREAD_PRIORITY_DEFAULTThread inferenceThread new Thread(() - { // 推理代码 }); inferenceThread.setPriority(Thread.currentThread().getPriority() 2); inferenceThread.start();注意不能设为THREAD_PRIORITY_URGENT_AUDIO否则会触发系统ANR。元凶三GPU驱动Bug某国产平板Allwinner A64的Mali-400 MP2 GPU在启用OpenGL ES 3.0后glReadPixels返回全黑。解决方案强制降级到OpenGL ES 2.0并在AndroidManifest.xml中添加application android:hardwareAcceleratedfalse ... 虽然牺牲了部分UI动画但确保了推理数据流的纯净性。4.3 MQTT连接“假在线”陷阱与心跳保活实战MQTT的QoS机制常被误解。我们曾交付的某批设备在弱网环境下出现“设备显示在线但控制指令永不抵达”的情况。根因是客户端发送了CONNECT包服务端返回CONNACK但后续PINGREQ/PINGRESP心跳包因网络抖动丢失服务端已断开连接而客户端仍认为在线。终极保活方案客户端侧setKeepAliveInterval(30)但必须配合手动心跳检测。在MqttCallback.connectionLost()回调中不立即重连而是先ping局域网网关Override public void connectionLost(Throwable cause) { // 先检测网络连通性 if (isNetworkAvailable()) { // 网络正常大概率是服务端问题延迟5秒重连 handler.postDelayed(reconnectRunnable, 5000); } else { // 网络异常启动WiFi扫描重连流程 startWifiReconnect(); } }服务端侧在Mosquitto配置中max_keepalive 60单位秒并启用persistent_client_expiration 1h防止僵尸连接占满连接数。应用层兜底在Android端每30秒向pet/health/{device_id}发布一次心跳包内容为{uptime:12345,battery:87}。喂食器固件监听此Topic若120秒未收到则主动断电重启。注意所有MQTT Topic中的{device_id}必须用Build.SERIALAndroid 10已废弃的替代方案——我们读取/proc/cpuinfo中的Serial字段或fallback到Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID)。这是规避Android隐私政策的合规做法。5. 硬件适配与量产要点从实验室Demo到万台设备的跨越5.1 主控芯片选型红黑榜哪些SoC真的适合猫脸识别不是所有Android SoC都适合跑AI视觉。我们基于200台设备实测给出选型建议推荐已量产验证高通QCM22904nm工艺NPU算力3.2TOPS支持INT8量化模型直接加载。最大优势Camera ISP对低照度猫脸优化极佳凌晨1点室内无补光下检测率仍达89%。缺点成本高适合中高端产品。瑞芯微RK332628nmCPU性能一般但内置NPU0.8TOPS专为轻量模型优化。我们移植的1.8MB模型在其上推理耗时仅210ms功耗仅1.2W。性价比之王已用于某品牌智能猫砂盆月销2万台。谨慎选择需深度适配联发科MT8765B常见于安卓电视盒子。问题在于Camera HAL对YUV格式支持不全必须降级到Camera1 API。我们为此专门写了HAL层Patch工作量相当于重写驱动。结论除非已有成熟驱动否则不推荐。全志H616价格诱人但GPU Mali-G31不支持OpenGL ES 3.0导致部分OpenCV加速失效。必须全程CPU推理帧率仅6fps勉强可用。黑名单明确不推荐展讯SC9863A内存带宽仅1.6GB/s加载1.8MB模型需1.8秒且频繁触发Low Memory Killer。实测连续运行2小时后系统直接重启。华为Hi3516DV300虽为海思神U但Android SDK支持极差官方未提供CameraX适配层强行移植会导致预览花屏率超30%。实操心得选型时不要只看CPU主频和核数重点查三点1ISP对低照度图像处理能力2NPU对INT8模型的原生支持度3Camera HAL对YUV_420_888格式的buffer管理机制。这三点决定了项目是“能跑”还是“能卖”。5.2 量产固件烧录与OTA升级的生死线实验室跑通≠量产可靠。我们为某客户做的OTA升级曾因一个字节的疏忽导致500台设备变砖。血泪总结分区表设计必须预留recovery和misc分区。recovery用于紧急刷机misc存储升级状态如ota_statussuccess。若省略misc升级中断时无法判断是否需回滚设备将无限循环在bootloader。升级包签名Android要求OTA包必须用私钥签名。生成密钥命令keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-key-alias签名命令java -Xmx2048m -Djava.library.pathout/host/linux-x86/lib64 -jar out/host/linux-x86/framework/signapk.jar \ build/target/product/security/testkey.x509.pem \ build/target/product/security/testkey.pk8 \ out/target/product/rk3326/obj/PACKAGING/target_files_intermediates/xxx-target_files.zip \ xxx-ota-update.zip升级过程防呆在updater-script中加入空间检查# 检查/data分区剩余空间 if ! is_mounted(/data); then mount(/data); endif; if getprop(ro.product.device) rk3326; then if get_partition_size(/data) 536870912; then # 小于512MB abort(ERROR: /data partition too small for update!); endif; endif;这个检查避免了因用户装了太多APP导致升级失败的客诉。降级保护在AndroidManifest.xml中android:versionCode必须严格递增。若客户要求“降级到旧版”必须在build.gradle中修改versionCode为更大值再打包。否则系统会拒绝安装提示“Package conflicts with an existing package”。6. 性能压测与真实场景验收用数据说话而非“看起来可以”6.1 压力测试方案模拟家庭环境的极限挑战实验室环境永远比真实家庭温和。我们设计了四级压力测试一级基础功能设备连续运行72小时每分钟识别一次记录内存泄漏adb shell dumpsys meminfo com.example.catrecog | grep TOTAL24小时增长≤5MB为合格。CPU占用adb shell top -n 1 | grep catrecog平均≤35%为合格。电池消耗在Pixel 4a4080mAh上待机识别72小时耗电≤28%即每天≤12%。二级弱网环境用adb shell settings put global captive_portal_mode 0关闭网络检测再用adb shell svc data disable模拟断网。测试断网30分钟后恢复MQTT是否自动重连重连时间≤8秒为合格。断网期间本地识别是否持续识别结果是否缓存缓存队列≥10条为合格。三级多猫混战在客厅同时放出三只猫用手机广角镜头拍摄测试同一帧内最多识别几只猫实测RK3326平台最高支持4只1080p输入。识别混淆率C1被误识为C2的次数/总识别C1次数要求≤3%。四级极端光照用照度计测量黎明50lux检测率≥85%正午窗边5000lux检测率≥92%夜间5lux无补光检测率≥65%此时启用算法增强直方图均衡化伽马校正所有测试数据必须形成《CatFaceRecognition_StressTest_Report_v1.2.pdf》作为交付物。客户签收前必须现场演示四级测试缺一不可。6.2 用户验收清单UAT Checklist让技术语言变成用户语言再好的技术用户看不懂就是零。我们把技术指标翻译成用户能感知的语言技术指标用户语言描述验收方式合格标准识别延迟 ≤350ms“当你家猫走到摄像头前喂食器在它还没反应过来时就已打开”用高速摄像机120fps录制识别全过程测量从猫脸进入画面到食槽电机启动的时间视频分析显示时间≤0.35秒误识率 ≤6.3%“连续喂食100次最多有6次喂错了猫”让用户随机挑选一只猫连续触发100次识别记录错误次数错误次数≤6续航 ≥30天“充一次电管一个月不用天天想着充电”设备满电开机关闭屏幕仅运行识别MQTT记录电量从100%掉到20%的时间时间≥30天离线可用“就算家里断网了它也能认出猫只是不通知你而已”拔掉路由器网线让用户操作识别观察屏幕是否显示猫名显示正确猫名无Crash最后分享一个小技巧UAT演示时永远准备一只“明星猫”——毛色独特、性格温顺、正脸率高的猫。我们合作的某品牌用一只三花猫做发布会演示100次识别全对现场客户掌声雷动。技术是骨体验是肉二者缺一不可。
返回列表