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

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 + 0println "OpenCV: " +openCVersionName + " " + project.buildscript.sourceFileandroid {
//    namespace 'org.opencv'compileSdkVersion 31defaultConfig {minSdkVersion 21targetSdkVersion 31versionCode openCVersionCodeversionName openCVersionNameexternalNativeBuild {cmake {arguments "-DANDROID_STL=c++_shared"targets "opencv_jni_shared"}}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}buildTypes {debug {packagingOptions {doNotStrip '**/*.so'  // controlled by OpenCV CMake scripts}}release {packagingOptions {doNotStrip '**/*.so'  // controlled by OpenCV CMake scripts}minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'}}lintOptions {checkReleaseBuilds falseabortOnError 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 assemblefrom('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";elsesuffix = 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);// 创建一个用于写入文件的FileOutputStreamtry (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;} elsereturn 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();elsereturn 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 {@Overrideprotected 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 assemblefrom('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.rhkb.cn/news/422677.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《OpenCV计算机视觉》—— 对图片进行旋转的两种方法

文章目录 一、用numpy库中的方法对图片进行旋转二、用OpenCV库中的方法对图片进行旋转 一、用numpy库中的方法对图片进行旋转 numpy库中的 np.rot90 函数方法可以对图片进行旋转 代码实现如下&#xff1a; import cv2 import numpy as np# 读取图片 img cv2.imread(wechat.jp…

Golang | Leetcode Golang题解之第400题第N位数字

题目&#xff1a; 题解&#xff1a; func findNthDigit(n int) int {d : 1for count : 9; n > d*count; count * 10 {n - d * countd}index : n - 1start : int(math.Pow10(d - 1))num : start index/ddigitIndex : index % dreturn num / int(math.Pow10(d-digitIndex-1)…

【三刷C语言】各种注意事项

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;C语言入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 目录 1.…

无线领夹麦克风怎么挑选?选购麦克风必看的避坑指南

在这个视频自媒体行业的蓬勃发展的时代&#xff0c;麦克风对于自媒体行业可以说是必不可少的装备了&#xff0c;在各大视频博主、Up主、主播通过互联网输出自己想法的同时&#xff0c;一个好的麦克风可以让事情事半功倍&#xff0c;通过麦克风提高收音音质&#xff0c;减少后期…

数据结构算法——排序算法

1.排序 1.选择排序 不稳定&#xff0c;一般不用&#xff0c;基本排序 思路&#xff1a;过滤数组&#xff0c;找到最小数&#xff0c;放在前面。 不稳&#xff1a;导致原本在前的数据移动到后面。 int arr[];for(i0;i<arr.length-1;i){int smallesti; for(ji1;j<leng…

【OpenCV】灰度化和二值化处理图像

文章目录 1. 图像灰度化处理对比2. 代码示例3. 二值化处理 1. 图像灰度化处理对比 2. 代码示例 #include <opencv2/opencv.hpp> using namespace cv;int main() {Mat currentImage imread("path_to_image.jpg"); // 读取彩色图像Mat grayImage;// 将彩色图像…

SOMEIP_ETS_106: SD_ClientServiceSubscribeEventgroup

测试目的&#xff1a; 验证DUT在客户端模式下能够订阅测试器提供的ETS&#xff08;Enhanced Testability Service&#xff09;服务。 描述 本测试用例旨在确保DUT在客户端模式下能够通过发送FindService消息发现服务&#xff0c;并在接收到测试器提供的OfferService消息后&a…

大模型如何生成下一个token--解码策略

Background 生成模型目前主要使用自回归&#xff08;Autoregressive&#xff09;模型&#xff0c;通过上文信息预测下文信息&#xff0c;如GPT系列&#xff1b; BERT系列使用自编码&#xff08;AutoEncode&#xff09;模型&#xff0c;在输入中随机mask一部分token&#xff0c…

关于ansible自动化运维工具

成长路上不孤单&#x1f60a;【14后&#xff0c;C爱好者&#xff0c;持续分享所学&#xff0c;如有需要欢迎收藏转发&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#xff01;&#xff01;&#xff01;&#xff01;&#xff…

HCIE和CCIE,哪个含金量更高点?

在现在内卷的大环境下&#xff0c;技术岗可谓人人自危&#xff0c;也因此各种认证的重视程度直线升高。 特别是华为认证的HCIE和思科认证的CCIE&#xff0c;它们都代表着网络技术领域的顶尖水平。 但面对这两个高含金量的认证&#xff0c;不得不让人问出这个问题&#xff1a;同…

关于Hadoop重新格式化之后集群的崩溃问题

关于Hadoop重新格式化之后集群的崩溃问题 文章目录 关于Hadoop重新格式化之后集群的崩溃问题写在前面版本信息实验场景 HiveHive交互段查询报错原因分析解决方法手动启动元数据服务重新初始化元数据库 HBase清理虚拟机磁盘参考资料 写在前面 版本信息 Linux版本&#xff1a;C…

ListBox显示最新数据、左移和右移操作

1、程序 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using static Sys…

基于SSM的二手交易管理系统的设计与实现 (含源码+sql+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的二手交易管理系统1拥有两种角色 管理员&#xff1a;商品管理、订单管理、充值管理、用户管理等用户&#xff1a;发布商品、查看闲置、充值账户、查看所有订单、发布求购信息、修…

Redis Sentinel(哨兵)详解

目录 一&#xff1a;什么是Sentinel&#xff08;哨兵&#xff09; 二&#xff1a;Sentinel有什么用 1.监控 2.故障转移 3通知 4.配置提供 三&#xff1a;Sentinel如何检测master节点宕机 1.主观下线 2.客观下线 四&#xff1a;Sentinel是如何选举出新的master 1.s…

SpringBoot3整合ELK实现日志可视化

SpringBoot整合ELK实现日志可视化 一、环境准备 Elasticsearch、Logstash、Kibana,组合起来可以搭建线上日志系统 ELK中各个服务的作用 Elasticsearch:用于存储收集到的日志信息&#xff1b; Logstash:用于收集日志&#xff0c;SpringBoot应用整合了Logstash以后会把日志发…

golang面试

算法&#xff1a; 1.提取二进制位最右边的 r i & (~i 1) 2.树上两个节点最远距离&#xff0c;先考虑头结点参与不参与。 3.暴力递归改dp。 1.确定暴力递归方式。 2.改记忆化搜索 3.严格表方式&#xff1a; 分析可变参数变化范围&#xff0c;参数数量决定表维度、 …

【文心智能体】通过工作流使用知识库来实现信息查询输出,一键查看旅游相关信息,让出行多一份信心

欢迎来到《小5讲堂》 这是《文心智能体平台》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 创建灵感基本配置头像名称和简介人物设定角色与目标思考路…

Android10源码刷入Pixel2以及整合GMS

一、ASOP源码下载 具体可以参考我之前发布的文章 二、下载相关驱动包 这一步很关键,关系到编译后的镜像能否刷入后运行 下载链接:Nexus 和 Pixel 设备的驱动程序二进制文件 如下图所示,将两个驱动程序上传到Ubuntu服务器,并进行解压,得到两个脚本: 下载解压后会有两…

MySQL数据的增删改查(一)

目录 新增&#xff08;create&#xff09; 插入单条记录 插入多条记录 查询&#xff08;retrieve&#xff09; 查询所有列 查询特定列 查询字段为表达式 别名 去重 排序 按单列排序 按多列排序 使用表达式或别名排序 排序NULL值 条件查询 比较运算符 逻辑运算…

【阿一网络安全】如何让你的密码更安全?(三) - 散列函数

散列函数 散列函数&#xff08;Hash Function&#xff0c;又称散列算法、哈希函数&#xff09;&#xff0c;是一种从任何一种数据中创建小的数字指纹的方法。 散列值 散列函数&#xff0c;把任意长的消息明文&#xff0c;压缩成摘要&#xff0c;使得数据量变小&#xff0c;将…