如何快速拥有自己的虚拟形象?

news2024/11/23 21:17:07

元宇宙(Metaverse),是人类运用数字技术构建的,由现实世界映射或超越现实世界,可与现实世界交互的虚拟世界,具备新型社会体系的数字生活空间。

可见元宇宙第一步是创建专属虚拟形象,但创建3D虚拟形象需要3D基础知识。对于大部分android开发者(包括我本人)来说没有这方面的积累。难道因此我们就难以进入元宇宙的世界吗?不,今天我们借助即构平台提供的Avatar SDK,只要有Android基础即可进入最火的元宇宙世界!先看效果:

请添加图片描述

上面gif被压缩的比较狠,这里放一张截图:

请添加图片描述

1 免费注册即构开发者

前往即构控制台网站:https://console.zego.im/注册开发者账户。注册成功后,创建项目:

请添加图片描述

控制台中可以得到AppID和AppSign两个数据,这两个数据是重要凭证,后面会用到。

由于我们用到了即构的Avatar功能,但目前官方没有提供线上自动开启方式,需要主动找客服申请(当然,这是免费的),只需提供自己项目的包名,即可开通Avatar权限。打开https://doc-zh.zego.im/article/15206右下角有“联系我们”,点击即可跟客服申请免费开通权限。

注意,如果不向客服申请Avatar权限,调用AvatarSDK会失败!

2 准备开发环境

前往即构官方元宇宙开发SDK网站https://doc-zh.zego.im/article/15302下载SDK,得到如下文件列表:

请添加图片描述

接下来过程如下:

  1. 打开SDK目录,将里面的ZegoAvatar.aar拷贝至app/libs目录下。
  2. 添加SDK引用。打开app/build.gradle文件,在dependencies节点引入 libs下所有的jaraar:

dependencies {
 
    implementation fileTree(dir: 'libs', include: ['*.jar', "*.aar"]) //通配引入
    
    //其他略
}
  1. 设置权限。根据实际应用需要,设置应用所需权限。进入app/src/main/AndroidManifest.xml 文件,添加权限。
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<uses-feature
    android:glEsVersion="0x00020000"
    android:required="true" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

因为Android 6.0在一些比较重要的权限上要求必须申请动态权限,不能只通过 AndroidMainfest.xml文件申请静态权限。具体动态请求权限代码可看附件源码。

3 导入资源

上一小节下载的zip文件中,有个assets目录。里面包含了Avatar形象相关资源,如:衣服、眉毛、鞋子等。这是即构官方免费提供的资源,可以满足一般性需求了。当然了,如果想要自己定制资源也是可以的。assets文件内容如下:

请添加图片描述

资源名称说明
AIModel.bundleAvatar 的 AI 模型资源。当使用表情随动、声音随动、AI 捏脸等能力时,必须先将该资源的绝对路径设置给 Avatar SDK。
base.bundle美术资源,包含基础 3D 人物模型资源、资源映射表、人物模型默认外形等。
Packages美妆、挂件、装饰等资源。 每个资源 200 KB ~ 1 MB 不等,跟资源复杂度相关。

注意:由于资源文件很大,上面下载的美术资源只包含少量必须资源。如果需要全部资源,可以去官网找客服索要:https://doc-zh.zego.im/article/14882

上面的文件需要存放到Android本地SDCard上,这里有2个方案可供参考:

  • 方案一: 将资源先放入到app/src/assets目录内,然后在app启动时,自动将assets的内容拷贝到SDcard中。
  • 方案二: 将资源放入到服务器端,运行时自动从服务器端下载。

为了简单起见,我们这里采用方案一。参考代码如下, 详细代码可以看附件:

AssetsFileTransfer.copyAssetsDir2Phone(this.getApplication(),
            "AIModel.bundle", "assets");
AssetsFileTransfer.copyAssetsDir2Phone(this.getApplication(),
            "base.bundle", "assets");
AssetsFileTransfer.copyAssetsDir2Phone(this.getApplication(),
            "Packages", "assets");

4 创建虚拟形象

创建虚拟形象本质上来说就是调用即构的Avatar SDK,其大致流程如下:

请添加图片描述

接下来我们逐步实现上面流程。

需要注意的是,上面示意图中采用的是AvatarView,可以非常方便的直接展示Avatar形象,但是不方便后期将画面通过RTC实时传递, 因此,我们后面的具体实现视通过TextureView替代AvatarView。

4.1 申请权鉴

这里再次强调一下,一定要打开https://doc-zh.zego.im/article/15206点击右下角有“联系我们”,向客服申请免费开通Avatar权限。否则无法使用Avatar SDK

申请权鉴代码如下:


public class KeyCenter { 
    // 控制台地址: https://console.zego.im/dashboard 
    public static long APP_ID = 这里值可以在控制台查询,参考第一节;  //这里填写APPID
    public static String APP_SIGN =  这里值可以在控制台查询,参考第一节; 
    // 鉴权服务器的地址
    public final static String BACKEND_API_URL = "https://aieffects-api.zego.im?Action=DescribeAvatarLicense";

    public static String avatarLicense = null;
    public static String getURL(String authInfo) {
        Uri.Builder builder = Uri.parse(BACKEND_API_URL).buildUpon();
        builder.appendQueryParameter("AppId", String.valueOf(APP_ID));
        builder.appendQueryParameter("AuthInfo", authInfo);

        return builder.build().toString();
    }

    public interface IGetLicenseCallback {
        void onGetLicense(int code, String message, ZegoLicense license);
    }

    /**
     * 在线拉取 license
     * @param context
     * @param callback
     */
    public static void getLicense(Context context, final IGetLicenseCallback callback) {
        requestLicense(ZegoAvatarService.getAuthInfo(APP_SIGN, context), callback);
    }

    /**
     * 获取license
     * */
    public static void requestLicense(String authInfo, final IGetLicenseCallback callback) {

        String url = getURL(authInfo);

        HttpRequest.asyncGet(url, ZegoLicense.class, (code, message, responseJsonBean) -> {
            if (callback != null) {
                callback.onGetLicense(code, message, responseJsonBean);
            }
        });
    }


    public class ZegoLicense {
        @SerializedName("License")
        private String license;
        public String getLicense() {
            return license;
        }
        public void setLicense(String license) {
            this.license = license;
        }
    }

}

在获取Lincense时,只需调用getLicense函数,例如在Activity类中只需如下调用:

KeyCenter.getLicense(this, (code, message, response) -> {
        if (code == 0) {
            KeyCenter.avatarLicense = response.getLicense();
            showLoading("正在初始化...");
            avatarMngr = AvatarMngr.getInstance(getApplication());
            avatarMngr.setLicense(KeyCenter.avatarLicense, this);
        } else {
            toast("License 获取失败, code: " + code);
        }
    });

4.2 初始化AvatarService

初始化AvatarService过程比较漫长(可能要几秒),通过开启worker线程后台加载以避免主线程阻塞。因此我们定义一个回调函数,待完成初始化后回调通知:


public interface OnAvatarServiceInitSucced {
    void onInitSucced();
}

public void setLicense(String license, OnAvatarServiceInitSucced listener) {
    this.listener = listener;
    ZegoAvatarService.addServiceObserver(this);
    String aiPath = FileUtils.getPhonePath(mApp, "AIModel.bundle", "assets"); //   AI 模型的绝对路径
    ZegoServiceConfig config = new ZegoServiceConfig(license, aiPath);
    ZegoAvatarService.init(mApp, config);
}

@Override
public void onStateChange(ZegoAvatarServiceState state) {
    if (state == ZegoAvatarServiceState.InitSucceed) {
        Log.i("ZegoAvatar", "Init success");
        // 要记得及时移除通知
        ZegoAvatarService.removeServiceObserver(this);
        if (listener != null) listener.onInitSucced();
    }
}

这里setLicense函数内完成初始化AvatarService,初始化完成后会回调onStateChange函数。但是要注意,在初始化之前必须把资源文件拷贝到本地SDCard,即完成资源导入:

private void initRes(Application app) {
    // 先把资源拷贝到SD卡,注意:线上使用时,需要做一下判断,避免多次拷贝。资源也可以做成从网络下载。
    if (!FileUtils.checkFile(app, "AIModel.bundle", "assets"))
        FileUtils.copyAssetsDir2Phone(app, "AIModel.bundle", "assets");
    if (!FileUtils.checkFile(app, "base.bundle", "assets"))
        FileUtils.copyAssetsDir2Phone(app, "base.bundle", "assets");
    if (!FileUtils.checkFile(app, "human.bundle", "assets"))
        FileUtils.copyAssetsDir2Phone(app, "human.bundle", "assets");
    if (!FileUtils.checkFile(app, "Packages", "assets"))
        FileUtils.copyAssetsDir2Phone(app, "Packages", "assets");
}

4.3 创建虚拟形象

前面在https://doc-zh.zego.im/article/15302下载SDK包含了helper目录,这个目录里面有非常重要的两个文件:

在这里插入图片描述

其中ZegoCharacterHelper文件是个接口定义类,即虽然是个类,但具体的实现全部在ZegoCharacterHelperImpl中。我们先一睹为快,看看ZegoCharacterHelper包含了哪些可处理的属性:

public class ZegoCharacterHelper {
    public static final String MODEL_ID_MALE = "male";
    public static final String MODEL_ID_FEMALE = "female";
    //****************************** 捏脸维度的 key 值 ******************************/
    public static final String FACESHAPE_BROW_SIZE_Y = "faceshape_brow_size_y";// 眉毛厚度, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_BROW_SIZE_X = "faceshape_brow_size_x";// 眉毛长度, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_BROW_ALL_Y = "faceshape_brow_all_y";// 眉毛高度, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_BROW_ALL_ROLL_Z = "faceshape_brow_all_roll_z";// 眉毛旋转, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_EYE_SIZE = "faceshape_eye_size"; // 眼睛大小, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_EYE_SIZE_Y = "faceshape_eye_size_y";
    public static final String FACESHAPE_EYE_ROLL_Y = "faceshape_eye_roll_y";// 眼睛高度, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_EYE_ROLL_Z = "faceshape_eye_roll_z";// 眼睛旋转, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_EYE_X = "faceshape_eye_x";// 双眼眼距, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_NOSE_ALL_X = "faceshape_nose_all_x";// 鼻子宽度, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_NOSE_ALL_Y = "faceshape_nose_all_y";// 鼻子高度, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_NOSE_SIZE_Z = "faceshape_nose_size_z";
    public static final String FACESHAPE_NOSE_ALL_ROLL_Y = "faceshape_nose_all_roll_y";// 鼻头旋转, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_NOSTRIL_ROLL_Y = "faceshape_nostril_roll_y";// 鼻翼旋转, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_NOSTRIL_X = "faceshape_nostril_x";
    public static final String FACESHAPE_MOUTH_ALL_Y = "faceshape_mouth_all_y";// 嘴巴上下, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_LIP_ALL_SIZE_Y = "faceshape_lip_all_size_y";// 嘴唇厚度, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_LIPCORNER_Y = "faceshape_lipcorner_y";// 嘴角旋转, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_LIP_UPPER_SIZE_X = "faceshape_lip_upper_size_x"; // 上唇宽度, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_LIP_LOWER_SIZE_X = "faceshape_lip_lower_size_x"; // 下唇宽度, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_JAW_ALL_SIZE_X = "faceshape_jaw_all_size_x";// 下巴宽度, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_JAW_Y = "faceshape_jaw_y";// 下巴高度, 取值范围0.0-1.0,默认值0.5
    public static final String FACESHAPE_CHEEK_ALL_SIZE_X = "faceshape_cheek_all_size_x";// 脸颊宽度, 取值范围0.0-1.0,默认值0.5

    //其他函数略
}

可以看到,上面数据基本包含所有人脸属性了,基本具备了捏脸能力,篇幅原因,我们这里不具体去实现。有这方面需求的读者,可以通过在界面上调整上面相关属性来实现。

接下来我们开始创建虚拟形象,首先创建一个User实体类:


public class User {
    public String userName; //用户名
    public String userId; //用户ID
    public boolean isMan; //性别
    public int width; //预览宽度
    public int height; //预览高度
    public int bgColor; //背景颜色
    public int shirtIdx = 0; // T-shirt资源id
    public int browIdx = 0; //眉毛资源id

    public User(String userName, String userId, int width, int height) {
        this.userName = userName;
        this.userId = userId;
        this.width = width;
        this.height = height;
        this.isMan = true;
        bgColor = Color.argb(255, 33, 66, 99);
    }  
}

示例作用,为了简单起见,我们这里只针对眉毛和衣服资源做选取。接下来创建一个Activity:

public class AvatarActivity extends BaseActivity  {
    private int vWidth = 720;
    private int vHeight = 1080; 
    private User user = new User("C_0001", "C_0001", vWidth, vHeight);
    private TextureView mTextureView;  //用于显示Avatar形象
    private AvatarMngr mAvatarMngr; // 用于维护管理Avatar
    private ColorPickerDialog colorPickerDialog; //用于背景色选取

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_avatar);
        
        // ....
        // 其他初始化界面相关代码略...
        // ....

        user.isMan = true;
        mTextureView = findViewById(R.id.avatar_view);  
        mZegoMngr = ZegoMngr.getInstance(getApplication()); 
        // 开启虚拟形象预览
        mAvatarMngr.start(mTextureView, user);
    }

最后一行代码中开启了虚拟形象预览,那么这里开启虚拟形象预览具体做了哪些工作呢?show me the code:

public class AvatarMngr implements ZegoAvatarServiceDelegate, RTCMngr.CaptureListener {
    private static final String TAG = "AvatarMngr";
    private static AvatarMngr mInstance;
    private boolean mIsStop = false;

    private User mUser = null;
    private TextureBgRender mBgRender = null;
    private OnAvatarServiceInitSucced listener;
    private ZegoCharacterHelper mCharacterHelper;
    private Application mApp;

    public interface OnAvatarServiceInitSucced {
        void onInitSucced();
    }

    public void setLicense(String license, OnAvatarServiceInitSucced listener) {
        this.listener = listener;
        ZegoAvatarService.addServiceObserver(this);
        String aiPath = FileUtils.getPhonePath(mApp, "AIModel.bundle", "assets"); //   AI 模型的绝对路径
        ZegoServiceConfig config = new ZegoServiceConfig(license, aiPath);
        ZegoAvatarService.init(mApp, config);
    }

    public void stop() {
        mIsStop = true;
        mUser = null;
        stopExpression();
    }

    public void updateUser(User user) {
        mUser = user;
        if (user.shirtIdx == 0) {
            mCharacterHelper.setPackage("m-shirt01");
        } else {
            mCharacterHelper.setPackage("m-shirt02");
        }
        if (user.browIdx == 0) {
            mCharacterHelper.setPackage("brows_1");
        } else {
            mCharacterHelper.setPackage("brows_2");
        }
    }

    /**
     * 启动Avatar,调用此函数之前,请确保已经调用过setLicense
     *
     * @param avatarView
     */
    public void start(TextureView avatarView, User user) {
        mUser = user;
        mIsStop = false;
        initAvatar(avatarView, user);
        startExpression();
    }

    private void initAvatar(TextureView avatarView, User user) {
        String sex = ZegoCharacterHelper.MODEL_ID_MALE;
        if (!user.isMan) sex = ZegoCharacterHelper.MODEL_ID_FEMALE;

        // 创建 helper 简化调用
        // base.bundle 是头模, human.bundle 是全身人模
        mCharacterHelper = new ZegoCharacterHelper(FileUtils.getPhonePath(mApp, "human.bundle", "assets"));
        mCharacterHelper.setExtendPackagePath(FileUtils.getPhonePath(mApp, "Packages", "assets"));
        // 设置形象配置
        mCharacterHelper.setDefaultAvatar(sex);
        updateUser(user);
        // 获取当前妆容数据, 可以保存到用户资料中
        String json = mCharacterHelper.getAvatarJson();

    }

    // 启动表情检测
    private void startExpression() {
        // 启动表情检测前要申请摄像头权限, 这里是在 MainActivity 已经申请过了
        ZegoAvatarService.getInteractEngine().startDetectExpression(ZegoExpressionDetectMode.Camera, expression -> {
            // 表情直接塞给 avatar 驱动
            mCharacterHelper.setExpression(expression);
        });
    }

    // 停止表情检测
    private void stopExpression() {
        // 不用的时候记得停止
        ZegoAvatarService.getInteractEngine().stopDetectExpression();
    }

    // 获取到 avatar 纹理后的处理
    public void onCaptureAvatar(int textureId, int width, int height) {
        if (mIsStop || mUser == null) { // rtc 的 onStop 是异步的, 可能activity已经运行到onStop了, rtc还没
            return;
        }
        boolean useFBO = true;
        if (mBgRender == null) {
            mBgRender = new TextureBgRender(textureId, useFBO, width, height, Texture2dProgram.ProgramType.TEXTURE_2D_BG);
        }
        mBgRender.setInputTexture(textureId);
        float r = Color.red(mUser.bgColor) / 255f;
        float g = Color.green(mUser.bgColor) / 255f;
        float b = Color.blue(mUser.bgColor) / 255f;
        float a = Color.alpha(mUser.bgColor) / 255f;
        mBgRender.setBgColor(r, g, b, a);
        mBgRender.draw(useFBO); // 画到 fbo 上需要反向的
        ZegoExpressEngine.getEngine().sendCustomVideoCaptureTextureData(mBgRender.getOutputTextureID(), width, height, System.currentTimeMillis());


    }

    @Override
    public void onStartCapture() {
        if (mUser == null) return;
//        // 收到回调后,开发者需要执行启动视频采集相关的业务逻辑,例如开启摄像头等
        AvatarCaptureConfig config = new AvatarCaptureConfig(mUser.width, mUser.height);
//        // 开始捕获纹理
        mCharacterHelper.startCaptureAvatar(config, this::onCaptureAvatar);
    }

    @Override
    public void onStopCapture() {
        mCharacterHelper.stopCaptureAvatar();
        stopExpression();
    }


    private void initRes(Application app) {
        // 先把资源拷贝到SD卡,注意:线上使用时,需要做一下判断,避免多次拷贝。资源也可以做成从网络下载。
        if (!FileUtils.checkFile(app, "AIModel.bundle", "assets"))
            FileUtils.copyAssetsDir2Phone(app, "AIModel.bundle", "assets");
        if (!FileUtils.checkFile(app, "base.bundle", "assets"))
            FileUtils.copyAssetsDir2Phone(app, "base.bundle", "assets");
        if (!FileUtils.checkFile(app, "human.bundle", "assets"))
            FileUtils.copyAssetsDir2Phone(app, "human.bundle", "assets");
        if (!FileUtils.checkFile(app, "Packages", "assets"))
            FileUtils.copyAssetsDir2Phone(app, "Packages", "assets");

    }

    @Override
    public void onError(ZegoAvatarErrorCode code, String desc) {
        Log.e(TAG, "errorcode : " + code.getErrorCode() + ",desc : " + desc);
    }

    @Override
    public void onStateChange(ZegoAvatarServiceState state) {
        if (state == ZegoAvatarServiceState.InitSucceed) {
            Log.i("ZegoAvatar", "Init success");
            // 要记得及时移除通知
            ZegoAvatarService.removeServiceObserver(this);
            if (listener != null) listener.onInitSucced();
        }
    }

    private AvatarMngr(Application app) {
        mApp = app;
        initRes(app);
    }

    public static AvatarMngr getInstance(Application app) {
        if (null == mInstance) {
            synchronized (AvatarMngr.class) {
                if (null == mInstance) {
                    mInstance = new AvatarMngr(app);
                }
            }
        }
        return mInstance;
    }
}

以上代码完成了整个虚拟形象的创建,关键代码全部展示,如果还需要具体全部代码,直接从附件中下载即可。

5 附件

  • 源码:https://github.com/RTCWang/Meta-Avatar
  • 即构元宇宙官网:https://doc-zh.zego.im/article/15302

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/87167.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android入门第45天-手工发送一个BroadCast

简介 上一篇我们讲了简单的动态BroadCast&#xff0c;今天我们通过手工来发送一条BroadCast进一步来了解BroadCast。 在上一篇里我们使用BroadCast监听网络状态&#xff0c;今天我们要完成的是自己发一条自自己的消息来触发BroadCast Receiver。 设计 为了让Receiver收听到…

蓝牙耳机无延迟哪款好?适合打游戏的无线蓝牙耳机

手机可以说是人手必备&#xff0c;随声得还有蓝牙耳机&#xff0c;随着3.5耳机孔得消失&#xff0c;蓝牙耳机可以说是现在得主流&#xff0c;无论哪个年龄段都可以佩戴蓝牙耳机&#xff0c;日常听歌、追剧&#xff0c;和朋友玩游戏佩戴蓝牙耳机&#xff0c;已经成为一种生活方式…

mybatis06:MyBatis的多表操作

目录 1.一对一关系 2.一对多查询 3.多对多查询 4例题演示 ​5.知识小结 1.一对一关系 2.一对多查询 3.多对多查询 4例题演示 前置准备 对应的依赖 <dependencies><!-- mysql驱动 --><dependency><groupId>mysql</groupId><artifactId&…

外汇交易:流行图表指标盘点

您所学到的关于交易的一切都像一种工具&#xff0c;已被添加到外汇交易者的工具箱中。当您在正确的时间使用正确的工具时&#xff0c;您的图表指标工具将为您提供更好的机会做出正确的交易决策。 布林带 布林带用来衡量市场的波动性。它们的作用类似于迷你支撑位和阻力位。 布…

MES系统为何与工厂数字化转型联系紧密

随着数字化技术的发展&#xff0c;MES系统的定义也是在不断的变化。但是&#xff0c;计划调度、质量管理、生产执行以及数据采集&#xff0c;一直都是MES的核心功能。 工厂数字化改造&#xff0c;对于制造业来说并不是一场革命。很多工厂在十年前就实现了车间设备的联网&#…

EXCEL基础:数据有效性设置与从身份证号码提取出生日期、性别操作

如下所示&#xff0c;为某公司的人员信息表&#xff0c;以下操作均是基于该表格&#xff0c;声明&#xff1a;该表格来自网络&#xff01; 下面进行【数据有效性】的设置&#xff1a; 先选中区域&#xff0c;弹出【数据有效性】对话框&#xff0c;在【设置】里的【允许】里输入…

win11设置java环境变量

python环境变量比java简单很多&#xff0c;而java比较麻烦&#xff0c;下面这些步骤应该是一步不能少&#xff0c;必须新建两个而且移动到最上面 一、找到设置环境变量 只要是windows系统&#xff0c;他就长这样&#xff0c;需要找到这个页面 很多之前的文章都会说&#xff1…

[附源码]Node.js计算机毕业设计电影院订票系统Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

六、作用域,作用域链,预编译,闭包基础

六、作用域&#xff0c;作用域链&#xff0c;预编译&#xff0c;闭包基础 使用AO,GO说明作用域和作用域链 AO与函数有关&#xff0c;函数能创造出独立的空间&#xff0c;但是这句话不太对&#xff0c;接下来就是解释&#xff1a; 对象 每个对象都有属性和方法&#xff1a; …

MobileNetV2原理说明及实践落地

本文参考&#xff1a; 轻量级网络——MobileNetV2_Clichong的博客-CSDN博客_mobilenetv2 1、MobileNetV2介绍 MobileNetV1主要是提出了可分离卷积的概念&#xff0c;大大减少了模型的参数个数&#xff0c;从而缩小了计算量。但是在CenterNet算法中作为BackBone效果并不佳&…

【MATLAB教程案例59】使用matlab实现基于LSTM网络的数据分类预测功能与仿真分析

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 目录 1.软件版本 2.LSTM网络理论概述

荧光点击试剂ICG-N3, ICG-azide,根据具体的需求进行定制, 避免频繁的溶解和冻干,取用时注意干燥

【英文名称】 ICG-azide&#xff0c;ICG-N3 【结 构 式】 【CAS】N/A 【分子式】C48H56N6O4S 【分子量】813.07 【基团】叠氮基基团 【纯度】95% 【规格】1mg&#xff0c;5mg&#xff0c;10mg 【是否接受定制】根据具体的需求进行定制 【外观】 绿色固体&#xff08;具…

最小二乘问题,,而不是方法

最小二乘是一大类问题&#xff0c;而不是一个简单的方法 适用于&#xff1a;线性&#xff08;非线性&#xff09;方程组问题&#xff0c;如果观测带有噪声&#xff0c;我们需要建立最小二乘模型。如果噪声符合高斯分布&#xff0c;即最小二乘问题的解对应于原问题的最大似然解…

千万不要做“舔狗式”营销

不知道在网上做生意的你们是否经常陷入我下面说的这几种尴尬境地&#xff1a;每天都在推广引流&#xff0c;每天都在发广告&#xff0c;但转化率却低得可怜。粉丝质量普遍不行&#xff0c;不精准&#xff0c;好不容易来几个粉&#xff0c;不是白嫖党就是垃圾粉。两句话不对头&a…

C++实现红外Fir谱图文件转BMP图片文件

1、红外图谱文件 红外图谱文件由文件头和温度数据两部分组成,其中文件头 64 个字节,其余字节为温度数据。 如下如: 每个像素用两个字节表示温度(16 位有符号短整数),低字节在前,高字节在后,温度数据单位为 0.1℃,温度数据共 w h 2 字节。文件头定义 如下: 从文件…

Android Studio 开发环境搭建 配置

前言 上一次做 Android 开发还是在大三的 Android 课设项目上「 IPOD - 本地音乐播放器」 开发环境&#xff1a;JDK开发语言&#xff1a;Java开发工具&#xff1a;Android Studio 现在由于工作需要 「面向业务编程」&#xff0c;需要重拾 Android 开发&#xff0c;由于电脑已换…

mysql基础学习(2)-regexp正则表达式的学习

表示例 user 学习&#xff1a; ^匹配以^后面字符开头的所有数据&#xff0c;示例&#xff1a;^str&#xff0c;则匹配str开头的所以数据$匹配以$前面面字符结尾的所有数据&#xff0c;示例&#xff1a;str$&#xff0c;则匹配str结尾的所以数据. 匹配任何单个字符&…

嵌入式分享合集121

一、Matter协议 不是广告啊就是看见了就搬来了 也没用过啊~ 早在2019年底&#xff0c;中国就已成为全球最大的智能家居消费国&#xff0c;占全球智能家居消费市场份额的50%-60%&#xff1b;2021年&#xff0c;中国智能家居市场规模约为5880亿元&#xff0c;同比增长12.7%&…

使用github的pages配合action自动部署vue项目

如果你需要某个 action&#xff0c;不必自己写复杂的脚本&#xff0c;直接引用他人写好的 action 即可&#xff0c;整个持续集成过程&#xff0c;就变成了一个 actions 的组合。这就是 GitHub Actions 最特别的地方。 GitHub 做了一个官方市场&#xff0c;可以搜索到他人提交的…

安全研究 # 课题:二进制成分分析(Binary SCA)

本文参考多篇文章写作而成&#xff0c;出处在文末注明(本文在课题开展过程中长期保持更新)。 二进制成分分析 SCA&#xff08;Software Composition Analysis&#xff09;软件成分分析&#xff0c;通俗的理解就是通过分析软件包含的一些信息和特征来实现对该软件的识别、管理、…