安卓学习笔记-unity调用原生opencv的sdk

news2024/11/24 3:12:57

unity调用原生opencv的sdk

  • 问题描述
  • 解决思路
  • 解决过程
    • 准备工作
      • opencv安卓原生sdk
      • 找到人脸检测的代码,检测成功后发送消息给unity
      • unity接收消息
    • 遇到的问题
      • 问题一
      • 问题二
      • 问题三
    • 如何解决遇到的问题
      • 问题一:opencvactivity遮挡unity的界面问题
      • 问题二:数据传输
      • 问题三:打包失败的一些解决方法
  • 其他注意事项

问题描述

情况描述:业务需求,在安卓系统上使用人脸检测功能,一开始使用的方案是在unity中直接调用unity资源商店中的插件“opencv-unity.unitypacage”,魔改一下关于人脸检测的脚本就可以用了。实际测试也没有问题
但是,在安卓广告机上使用发现会无缘无故闪退,经过一系列排查,最终确定是因为调用了webcamtexture之类的脚本,调取相机出现异常,导致的闪退
在网上找了两三天的帖子寻求帮助,发现这是安卓主板3588和3568不兼容unity的webcamtexture关于相机的api的脚本,因此在unity中直接使用插件进行人脸检测功能是没有什么可能了。找了技术支持也是提供不了什么解决方案的(想想也知道是个大工程…)

解决思路

那么怎么办呢?

我在排查的过程中发现,调用安卓原生的相机这些api时,这两个主板上是可以正常跑通的。(实际使用过·opencv安卓原生sdk·以及·facedetector·),调用原生相机没问题的话,那么只要将原生调用相机的插件接入到unity中不是就可以了吗?

解决过程

准备工作

为了印证该方案是否可行

  1. 需要确定opencv安卓原生的sdk能正常使用人脸检测功能,并且作为一个aar包被unity调用
  2. 找到人脸检测的代码,在识别到人脸的逻辑中发送消息给unity
  3. unity接收并处理信息(图片数据或图片路径)

opencv安卓原生sdk

在官网上下载安卓原生sdk包(SDK下载地址),然后导入到AndroidStudio中,由于它本身就是一个模块,所以很方便进行调用,在主模块中创建一个activity,继承sdk中的OpencvActivity,然后打包运行,会出现一个显示相机画面的界面,检测到人脸就会自动在人脸的部分绘制一个框框,这和unity的插件效果是一致的,然后打包到安卓主板(3588和3568)上进行测试,也是可以正常检测到的。这一步没问题
tips:需要自行处理gradle版本和compilesdk版本的问题,以及打包过程中可能出现的异常(需要在build.gradle中屏蔽掉一些代码,以及添加一些代码)时间问题就直接贴上来了

apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
//apply plugin: 'kotlin-android'
//apply plugin: 'kotlin-android-extensions'

def openCVersionName = "4.10.0"
def openCVersionCode = ((4 * 100 + 10) * 100 + 0) * 10 + 0

println "OpenCV: " +openCVersionName + " " + project.buildscript.sourceFile

android {
//    namespace 'org.opencv'
    compileSdkVersion 31

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 31

        versionCode openCVersionCode
        versionName openCVersionName

        externalNativeBuild {
            cmake {
                arguments "-DANDROID_STL=c++_shared"
                targets "opencv_jni_shared"
            }
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    buildTypes {
        debug {
            packagingOptions {
                doNotStrip '**/*.so'  // controlled by OpenCV CMake scripts
            }
        }
        release {
            packagingOptions {
                doNotStrip '**/*.so'  // controlled by OpenCV CMake scripts
            }
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
    lintOptions {
        checkReleaseBuilds false
        abortOnError false
    }

//    buildFeatures {
//        prefabPublishing true
//        buildConfig true
//    }
//    prefab {
//        opencv_jni_shared {
//            headers "native/jni/include"
//        }
//    }

    sourceSets {
        main {
            jniLibs.srcDirs = ['native/libs']
            java.srcDirs = ['java/src']
            res.srcDirs = ['java/res']
            manifest.srcFile 'java/AndroidManifest.xml'
        }
    }

//    publishing {
//        singleVariant('release') {
//            withSourcesJar()
//            withJavadocJar()
//        }
//    }

    externalNativeBuild {
        cmake {
            path (project.projectDir.toString() + '/libcxx_helper/CMakeLists.txt')
        }
    }
}

publishing {
    publications {
        release(MavenPublication) {
            groupId = 'org.opencv'
            artifactId = 'opencv'
            version = '4.10.0'

            afterEvaluate {
               from components.release
           }
        }
    }
    repositories {
        maven {
            name = 'myrepo'
            url = "${project.buildDir}/repo"
        }
    }
}

dependencies {
    implementation files('libs\\unity-classes.jar')

    implementation 'com.android.support:appcompat-v7:28.0.0'
//implementation files('libs\\unity-classes.jar')
}

task CopyPlugin(type: Copy) {
    dependsOn assemble
    from('build/outputs/aar')
    into('../../Assets/Plugins/Android')
    include(project.name + '-release.aar')
}

找到人脸检测的代码,检测成功后发送消息给unity

这一步比较简单,找到opencvactivity中的visualize方法,其中的faces.rows()就是检测到的人脸数量。目前我只需要它告诉我有人脸就行了,所以就直接在这个方法中发送消息给unity

UnityPlayer.UnitySendMessage("receiveObj", "faceresult", "人脸数量为" + faces.rows());

unity接收消息

这一步就更简单了
在场景中创建receiveObj这个对象,然后把脚本挂载到这个对象中,添加下面的代码


    public void faceresult(string path)
    {
    	//接收到消息之后的逻辑处理
    }

遇到的问题

那么实际上,在按照这个思路和方案执行的过程中,会遇到很多,很多,很多小问题

问题一

如果要调用人脸校测的脚本,就要使用到opencvactivity,而观察代码可发现它是一个activity,那么调用这个activity,势必会将unity 的activiy进行遮挡,导致无法点击和看到unity的界面,十分影响体验,可以说是十分致命的问题

问题二

传输数据。在opencvactivity中获取到的数据,需要转化成二进制数据或者base64或者图片路径然后再传给unity才能使用,实际上在使用过程中发现无法正确获取到二进制和base64这两个数据

问题三

原生的opencv插件打包成aar后,在unity中调用会出现些许问题,如无法正确找到opencvactivity,无法找到主题等异常
这几个是主要的问题,其他零散的可能一时间想不起来了

如何解决遇到的问题

问题一:opencvactivity遮挡unity的界面问题

这个问题在前面的方案思路中有提到,为了使用这个插件,就需要调用这个activity,但我本身unity就有一个activity了,如果调用这个activity的话,势必会暂停unity的界面,然后显示opencvactivity,从而导致体验感下降
解决方法:

  1. 隐藏这个activity
  2. 魔改这个activity,改成fragment或者dialog
    由于时间和能力有限,方法1是我的唯一选择
    那么这个方法在网上能找到很多解决方案
    我是这么解决的

1.1 在res的values文件夹中的style.xml和theme.xml文件中,添加关于透明主题和样式的信息
style.xml


    <style name="TranslucentActivity" parent="Theme.AppCompat.Light.NoActionBar">//无标题
        <item name="android:windowIsTranslucent">true</item>//透明
    </style>

theme.xml

<resources>

    <style name="TranslucentActivity.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="TranslucentActivity.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

    <style name="TranslucentActivity.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

1.2 在app的AndroidManifest.xml的application节点中添加


        android:theme="@style/TranslucentActivity"

1.3 在opencvactivity脚本中设置一些参数
需要将这个opencvactivity脚本对应的view进行隐藏,去掉点击事件和返回事件。也就是在onCreate方法的最后添加下面的代码

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);

        setContentView(R.layout.face_detect_surface_view);

        mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.fd_activity_surface_view);
//        mOpenCvCameraView.setVisibility(CameraBridgeViewBase.VISIBLE);
        mOpenCvCameraView.setCvCameraViewListener(this);

        // 设置透明沉浸状态栏
        if (Build.VERSION.SDK_INT >= 21) {
            View decorView = getWindow().getDecorView();
            decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); //使背景图与状态栏融合到一起,这里需要在setcontentview前执行
            getWindow().setStatusBarColor(Color.TRANSPARENT);
        }
        //设置1像素
        Window window = getWindow();
        window.setGravity(Gravity.LEFT | Gravity.TOP);
        WindowManager.LayoutParams params = window.getAttributes();
        params.x = 0;
        params.y = 0;
        params.height = 1;
        params.width = 1;
        window.setAttributes(params);

1.4 可能会出现的异常(attr之类的)
需要在app的builder.gradle中添加


    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'

通过以上步骤应该就可以实现将opencvactivity对应的view进行隐藏掉的功能了

问题二:数据传输

因为没时间研究sdk的逻辑和代码,所以我只是大概的看了一下,发现将获取到的人脸信息转成byte[]或者base64再传给unity的话,他的值始终是不变的。原因还没有研究,但是这个路子行不通的话就只能在sdk检测到人脸之后存到本地,然后返回给unity一个文件名,unity接收到文件名,在相应的路径下读取文件然后获取字节流了

  1. sdk保存图片到本地(路径我设置为了私有路径,也即是包名下的路径)
    在opencvactivity的visualize中添加savealum方法,savealbum方法和相应的其他方法如下
 public static String saveAlbum(Context context, Mat rbga, Bitmap.CompressFormat format, int quality, boolean recycle) {
        Bitmap bitmap = null;
        bitmap = Bitmap.createBitmap(rbga.cols(), rbga.rows(), Bitmap.Config.ARGB_8888);
        ByteArrayOutputStream byStream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, byStream);
        byte[] byteArray = byStream.toByteArray();
        Utils.matToBitmap(rbga, bitmap);
        String suffix;
        if (Bitmap.CompressFormat.JPEG == format)
            suffix = "JPG";
        else
            suffix = format.name();
        String fileName = System.currentTimeMillis() + "_" + quality + "." + suffix;
        if (Build.VERSION.SDK_INT < 29) {
            if (!isGranted(context)) {
                Log.e("ImageUtils", "save to album need storage permission");
                return null;
            }
            File picDir = Environment.getExternalStoragePublicDirectory("");
            File destFile = new File(context.getFilesDir(), fileName);
            if (!save(bitmap, destFile, format, quality, recycle))
                return null;
            Uri uri = null;
            if (destFile.exists()) {
                uri = Uri.parse("file://" + destFile.getAbsolutePath());
                Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                intent.setData(uri);
                context.sendBroadcast(intent);
            }
            return fileName;
        } else {
            // 获取内部存储的目录
            File dir = context.getExternalFilesDir(null);

            // 创建文件对象
            File file = new File(dir, fileName);

            // 创建一个用于写入文件的FileOutputStream
            try (FileOutputStream fos = new FileOutputStream(file)) {
                // 压缩图片到文件输出流中(这里以PNG格式为例)
                // 注意:你也可以选择其他格式,如JPEG,但需要使用不同的compress方法
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
                return fileName;
            } catch (IOException e) {
                e.printStackTrace();
                return "fail";
            }
        }

//        return Base64.encodeToString(byteArray, Base64.DEFAULT);
    }

    private static boolean save(Bitmap bitmap, File file, Bitmap.CompressFormat format, int quality, boolean recycle) {
        if (isEmptyBitmap(bitmap)) {
            Log.e("ImageUtils", "bitmap is empty.");
            return false;
        }
        if (bitmap.isRecycled()) {
            Log.e("ImageUtils", "bitmap is recycled.");
            return false;
        }
        if (!createFile(file, true)) {
            Log.e("ImageUtils", "create or delete file <$file> failed.");
            return false;
        }
        OutputStream os = null;
        boolean ret = false;
        try {
            os = new BufferedOutputStream(new FileOutputStream(file));
            ret = bitmap.compress(format, quality, os);
            if (recycle && !bitmap.isRecycled()) bitmap.recycle();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null)
                    os.close();
            } catch (IOException e) {
                // ignore
            }
        }
        return ret;
    }

    private static boolean isEmptyBitmap(Bitmap bitmap) {
        return bitmap == null || bitmap.isRecycled() || bitmap.getWidth() == 0 || bitmap.getHeight() == 0;
    }

    private static boolean createFile(File file, boolean isDeleteOldFile) {
        if (file == null) return false;
        if (file.exists()) {
            if (isDeleteOldFile) {
                if (!file.delete()) return false;
            } else
                return file.isFile();
        }
        if (!createDir(file.getParentFile())) return false;
        try {
            return file.createNewFile();
        } catch (IOException e) {
            return false;
        }
    }

    private static boolean createDir(File file) {
        if (file == null) return false;
        if (file.exists())
            return file.isDirectory();
        else
            return file.mkdirs();
    }

    private static boolean isGranted(Context context) {
        return (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE));
    }

保存本地图片的方法我是在网上扒的,网上在安卓10以后都可以访问到DCIM文件夹了,他们也大多都是使用的这个方法,但unity是不支持访问这些路径的,所以我还是改成了保存到报名下的私有路径中,这样unity就可以直接通过Application.persistdatapath这个api去访问文件了
然后只需要visulize这个方法中保存图片成功后,将图片名称返回给unity就可以了

问题三:打包失败的一些解决方法

问题三关于打包过程的问题在问题1中已经解决的差不多了,其他的可能就是一些版本问题,可以查看一下和我下面的信息是否有出入
需要实现一些样式的插件,否则在打包的时候会提示无法找到对应的UI属性的问题
因为compilesdkversion版本导致闪退的问题
在unity中可能会出现的无法正常拉取opencvactivity导致闪退的异常问题

其他注意事项

  1. 如果按照我的实现方法,就不能只在unity中打包,因为unity原生默认的主题是黑色的(涉及到unityplayeractivity这个类,而这个类里的方法都是在unityclass.jar中的,超出了我的能力范围),这个我尝试过改成透明的,这样会导致整个unityactivity都变成透明度的,这样是不合理的现象,所以你需要
    1.1 unity中打包成安卓工程,而不是apk。
    1.2 然后导入到androidstudio项目中,最好新建一个
    1.3 新建一个项目,然后在app的src中创建一个activity,这个activity的作用很简单,就是调用unityplayer这个脚本,如下
public class StartActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = new Intent(getApplicationContext(), UnityPlayerActivity.class);
        startActivity(intent);
    }
}
  1. opencv插件如何打包成aar包
    2.1 在插件模块中的build.gradle最后添加

task CopyPlugin(type: Copy) {
    dependsOn assemble
    from('build/outputs/aar')
    into('../../Assets/Plugins/Android')
    include(project.name + '-release.aar')
}

然后在androidstudio如下图点击对应的copyplguins然后运行即可
在这里插入图片描述
2.2 直接在如下图中点击生成
在这里插入图片描述
如果找不到上图的gradle的命令选项的话,需要在settings中打开,如下图
在这里插入图片描述
3. 小技巧
可以直接使用unity的library中的路径在androidstudio中直接打开unity打包的项目,然后使用androidstudio进行打包,不用unity的打包。如下图
在这里插入图片描述

找到对应的路径,然后在androidstudio中打开,就可以用androidstudio进行打包了
这一个的目的是因为unity中修改一些配置类的文件比较繁琐,所以可以直接在library中以androidstudio的方式打开安卓工程,然后以我们比较熟悉的界面去进行配置的修改和打包测试

  1. 在unity调用opencvactivity过程中,会出现unity界面内容停止的情况
    这个原因很简单,因为是从一个activity打开另一个activity,上一个activity是肯定会暂停的。那么为了体验感,就需要unity的activity保持运行。我的解决方法比较粗暴,不太优美。直接修改unity的unityplayeractivity这个脚本的生命周期。如下图,将onPause方法中的mUnityPlayer.onPause()这一行代码注释掉即可
    在这里插入图片描述

总结,以上就是大致实现通过opencv安卓原生的sdk插件实现人脸检测的同时不影响unity本身的activity运行,然后将检测到的信息保存到本地,通知unity,unity将信息读取出来进行处理的功能了。
这个流程走通大概花了4、5天左右,中间试错成本也不低。不过总算是能勉强解决这个因为安卓主板无法调用webcam相机然后闪退从而导致无法进行人脸检测的问题了。

太痛苦了…

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

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

相关文章

OpenAI官宣ChatGPT用户破2亿,新模型比GPT-4强100倍!

在刚刚召开的“KDDI SUMMIT 2024”大会上&#xff0c;OpenAI日本首席执行官長崎忠雄表示&#xff0c;截止至今年8月底&#xff0c;ChatGPT的月活用户数量突破2亿大关&#xff0c;成为史上最快达到这一成就的软件产品。 同时&#xff0c;ChatGPT企业级用户也获得大幅度增长达到…

【企业设置】自定义域名/应用模式支持自定义背景

09/04 主要更新模块概览 自定义背景 签名上传 权限配置 其他更新 01 表单管理 1.1 【移动端子表单】适配权限组字段操作权限 说明&#xff1a; 移动端子表单适配权限组配置&#xff0c;在权限组中可配置控制子表单的可新增记录&#xff0c;可插入记录&#xff0c…

24年9月通信基础知识补充1

看文献过程中不断发现有太多不懂的基础知识&#xff0c;故长期更新这类blog不断补充在这过程中学到的知识。由于这些内容与我的研究方向并不一定强相关&#xff0c;故记录不会很深入请见谅。 【通信基础知识补充2】9月通信基础知识补充1 一、Zadoff-Chu 序列1.1 Zadoff-Chu 序列…

【区块链 + 基层治理】腾讯未来社区:区块链业主决策系统 | FISCO BCOS应用案例

腾讯未来社区是腾讯推出的智慧社区综合解决方案&#xff0c;致力于形成“互联网 社区”一站式解决方案&#xff0c;打造智慧社 区健康生态。为了解决物业管理领域的痛点&#xff0c;构建围绕居民、物业、政府和商业四个角色为核心的良好生态&#xff0c;以 信息平台及工具为纽…

如何与客户保持高度粘性?这个系统给您答案

客户粘性是企业成功的关键因素之一&#xff0c;企客宝企微版在打通获客、转化、运营全链路方面发挥着重要作用&#xff0c;实现客户粘性的提升。 前言 客户粘性是企业成功的关键因素之一。企业需要不断通过各种手段提升客户粘性&#xff0c;保持客户忠诚度和长期合作关系。企客…

OpenCV结构分析与形状描述符(13)拟合椭圆函数fitEllipseDirect()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 围绕一组2D点拟合一个椭圆。 该函数计算出一个椭圆&#xff0c;该椭圆拟合一组2D点。它返回一个内切于该椭圆的旋转矩形。使用了由[91]提出的直接…

STM32F407+CubeMx串口通信实验(学习记录)

一、环境 硬件&#xff1a;STM32F407ZGT6开发板 软件&#xff1a;STM32CubeMx、Keil5 MDK、串口调试助手 PS:前面实验部分的代码都是可以正常运行的&#xff0c;但是在学习过程中我也踩了很多坑&#xff08;包括一些未弄明白的&#xff09;&#xff0c;我会记录在问题总结部分…

物联网之ESP32控制GPIO输出点亮LED、闪烁LED灯

MENU 前言原理GPIO引脚LED 硬件电路设计软件设计1、点亮一颗LED2、闪烁的LED 前言 不论学习什么单片机&#xff0c;最简单的外设莫过于IO口的高低电平控制LED&#xff0c;本文介绍如何使用Arduino控制ESP32的GPIO输出。通过本文的学习&#xff0c;掌握一定的Arduino程序架构知识…

求求你们别再跟风考PMP了!这几类人才真正需要这本证书!

救命啊&#xff01;&#xff01;&#xff01;这几天刷某书&#xff0c;看到好多人在吐槽说考了PMP证书一点用都没有&#xff0c;每三年还得花个千把块钱去续证&#xff0c;劝大家都别考。 这可能是PMP身上最大的一口“黑锅”了。作为全球认可、含金量极高的项目管理资格认证&am…

AXI4主机测试

前面对AXI4协议进行了比较详细的分析&#xff0c;本篇文章将会写一个主机代码来实现AXI4协议的时序。 设计思路&#xff1a;本次设计的主要目的是验证AXI4_FULL总线的时序&#xff0c;并且提升对AXI4_FULL总线协议的理解&#xff0c;因此可以采用状态机来控制&#xff0c;先向…

孩子用的台灯哪个牌子好?挑选护眼台灯先了解护眼台灯十大排名

孩子们的日常生活中有高达80%的时间是在阅读、做作业或面对电脑屏幕中度过的&#xff0c;因此对良好照明的需求显得尤为严格和精确。一些家长可能认为&#xff0c;只要孩子使用的是纸质材料&#xff0c;不像电子产品那样对眼睛有害&#xff0c;使用普通的台灯照明就足够了&…

mysql快速定位cpu 占比过高的sql语句

mysql快速定位cpu 占比过高的sql语句 当MySQL数据库的CPU使用率异常升高时&#xff0c;定位导致问题的SQL语句可以通过以下步骤进行 1、使用top命令找出mysl进程中占用CPU靠前的线程 #找出mysql 的进程号 ps -ef | grep mysql#根据进程号&#xff0c;找出占用CPU靠前的线程号…

【QT】文件读写,文件对话框

一.QFile类 QFile提供了从文件中读取和写入数据的能力。 我们通常会将文件路径作为参数传给QFile的构造函数。不过也可以在创建好对象最后&#xff0c;使用setFileName()来修改。 QFile是QIODevice的子类&#xff0c;利用QFile可以对文件进行打开&#xff0c;读取&#xff0c…

Bootstrap 5.3版本创建常用页面

你可以根据自己的主题需求来自定义样式。Bootstrap提供了丰富的CSS类和组件&#xff0c;使得创建响应式、移动优先的网站变得简单。下面我将指导你如何基于Bootstrap 5.3来编写一个简单但自定义主题的页面。 引入Bootstrap 首先&#xff0c;确保你的HTML文件中已经正确引入了B…

Vue学习:v-model绑定文本框、单选按钮、下拉菜单、复选框等

v-model指令可以在组件上使用以实现双向绑定&#xff0c;之前学习过v-model绑定文本框和下拉菜单&#xff0c;今天把表单的几个控件单选按钮radio、复选框checkbox、多行文本框textarea都试着绑定了一下。 一、单行文本框和多行文本框 <p>1.单行文本框</p> 用户名…

程序员日志之DNF编年史

目录 传送门正文日志1、概要2、超高度总结概括3、详细编年史3.1、大背景3.2、冒险家 传送门 SpringMVC的源码解析&#xff08;精品&#xff09; Spring6的源码解析&#xff08;精品&#xff09; SpringBoot3框架&#xff08;精品&#xff09; MyBatis框架&#xff08;精品&…

YOLOv5改进 | 模块缝合 | C3 融合RFCAConv增强感受野空间特征 【二次融合 小白必备】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录 &#xff1a;《YOLOv5入门 改…

飞牛fnOS安装KDE桌面

飞牛fnOS安装KDE桌面 这段时间新出的nas系统飞牛os真不错&#xff0c;基于debian的可折腾性又高了不少&#xff0c;今天就来给这个系统装个桌面&#xff0c;插上显示器也能当个电脑自己进自己的管理界面&#xff0c;播放下视频&#xff0c;上上网啥的。 文章目录 飞牛fnOS安装…

问卷调查,动静IP应该如何选择?

在探讨问卷调查这一领域时&#xff0c;选择使用动态IP还是静态IP&#xff0c;成为了许多从业者及市场研究者面临的重要决策&#xff0c;它不仅关乎数据收集的效率与质量&#xff0c;还直接影响到问卷调查的合法性与安全性。本文将从多个维度深入分析这两种IP类型的优劣&#xf…

python-网页自动化(三)

如果遇到使用 ajax 加载的网页&#xff0c;页面元素可能不是同时加载出来的&#xff0c;这个时候尝试在 get 方法执行完 成时获取网页源代码可能并非浏览器完全加载完成的页面。所以&#xff0c;这种情况下需要设置延时等待一定时间&#xff0c;确保全部节点都加载出来。 那么&…