当前位置: 首页 > news >正文

在基于Android相机预览的CV应用程序中使用 OpenCL

 查看:OpenCV系列文章目录(持续更新中......)

上一篇:OpenCV4.9.0在Android 开发简介

下一篇:在 MacOS 中安装

本指南旨在帮助您在基于 Android 相机预览的 CV 应用程序中使用 OpenCL ™。教程是为 Android Studio 2022.2.1 编写的。它已使用 Ubuntu 22.04 进行了测试。

本教程假定您已安装并配置了以下内容:

  • Android Studio (2022.2.1.+)
  • JDK 17
  • Android SDK
  • Android NDK (25.2.9519653+)
  • 从 github 或发布版下载 OpenCV 源代码,并按照 wiki 上的指令构建。

它还假定您熟悉 Android Java 和 JNI 编程基础知识。如果您需要上述任何方面的帮助,可以参考我们的 Android 开发简介指南。

本教程还假设您有一个启用了 OpenCL 的 Android 操作设备。

相关源代码位于 opencv/samples/android/tutorial-4-opencl 目录下的 OpenCV 示例中。

如何使用 OpenCL 构建自定义 OpenCV Android SDK

  1. 组装和配置 Android OpenCL SDK。示例的 JNI 部分依赖于标准的 Khornos OpenCL 标头,以及 OpenCL 和 libOpenCL.so 的C++包装器。标准的 OpenCL 标头可以从 OpenCV 存储库中的第三方目录或您的 Linux 分发包中复制。C++ 包装器可在 Github 上的官方 Khronos 存储库中找到。按以下方式将头文件复制到教学目录:
    cd your_path/ && mkdir ANDROID_OPENCL_SDK && mkdir ANDROID_OPENCL_SDK/include && cd ANDROID_OPENCL_SDK/include
    cp -r path_to_opencv/opencv/3rdparty/include/opencl/1.2/CL . && cd CL
    wget https://github.com/KhronosGroup/OpenCL-CLHPP/raw/main/include/CL/opencl.hpp
    wget https://github.com/KhronosGroup/OpenCL-CLHPP/raw/main/include/CL/cl2.hpp

    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

    libOpenCL.so 可以随 BSP 一起提供,也可以从任何具有相关架构的 OpenCL-cabaple Android 设备下载
    cd your_path/ANDROID_OPENCL_SDK && mkdir lib && cd lib
    adb pull /system/vendor/lib64/libOpenCL.so

    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

     libOpenCL.so 的系统版本可能有很多特定于平台的依赖关系。-Wl,--allow-shlib-undefined 标志允许忽略在构建过程中未使用的第三方符号。以下 CMake 行允许将 JNI 部件链接到标准 OpenCL,但不能将 loadLibrary 包含在应用程序包中。系统 OpenCL API 用于运行时。

target_link_libraries(${target} -lOpenCL)

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

使用 OpenCL 构建自定义 OpenCV Android SDK。默认情况下,OpenCL 支持 (T-API) 在 Android 操作系统的 OpenCV 构建中处于禁用状态。但可以在启用 OpenCL/T-API 的情况下在本地重建适用于 Android 的 OpenCV:CMake 的 use 选项。您还需要为 CMake 指定 Android OpenCL SDK: use 选项的路径。如果您正在使用 OpenCV 构建 OpenCV,请按照 wiki 上的说明进行操作。在 中设置这些 CMake 参数,例如:-DWITH_OPENCL=ON-DANDROID_OPENCL_SDK=path_to_your_Android_OpenCL_SDKbuild_sdk.py.config.pyndk-18-api-level-21.config.py

ABI("3", "arm64-v8a", None, 21, cmake_vars=dict('WITH_OPENCL': 'ON', 'ANDROID_OPENCL_SDK': 'path_to_your_Android_OpenCL_SDK'))

如果您使用 cmake/ninja 构建 OpenCV,请使用以下 bash 脚本(设置您的NDK_VERSION和路径,而不是路径示例):

cd path_to_opencv && mkdir build && cd build
export NDK_VERSION=25.2.9519653
export ANDROID_SDK=/home/user/Android/Sdk/
export ANDROID_OPENCL_SDK=/path_to_ANDROID_OPENCL_SDK/
export ANDROID_HOME=$ANDROID_SDK
export ANDROID_NDK_HOME=$ANDROID_SDK/ndk/$NDK_VERSION/
cmake -GNinja -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_shared -DANDROID_NATIVE_API_LEVEL=24
-DANDROID_SDK=$ANDROID_SDK -DANDROID_NDK=$ANDROID_NDK_HOME -DBUILD_JAVA=ON -DANDROID_HOME=$ANDROID_SDK -DBUILD_ANDROID_EXAMPLES=ON
-DINSTALL_ANDROID_EXAMPLES=ON -DANDROID_ABI=arm64-v8a -DWITH_OPENCL=ON -DANDROID_OPENCL_SDK=$ANDROID_OPENCL_SDK ..

前言

现在,通过 OpenCL 使用 GPGPU 来增强应用程序性能是一种相当现代的趋势。一些CV算法(例如图像过滤)在GPU上的运行速度比在CPU上快得多。最近,它在 Android 操作系统上已成为可能。

对于 Android 操作的设备,最流行的 CV 应用场景是在预览模式下启动相机,将一些 CV 算法应用于每个帧,并显示由该 CV 算法修改的预览帧。

让我们考虑一下如何在这种情况下使用 OpenCL。具体来说,让我们尝试两种方式:直接调用 OpenCL API 和最近引入的 OpenCV T-API(又名透明 API)——一些 OpenCV 算法的隐式 OpenCL 加速。

应用程序结构

启动 Android API 级别 11 (Android 3.0) 相机 API 允许使用 OpenGL 纹理作为预览帧的目标。Android API 级别 21 带来了一个新的 Camera2 API,它提供了对相机设置和使用模式的更多控制,它允许预览帧的多个目标,特别是 OpenGL 纹理。

在 OpenGL 纹理中拥有预览帧对于使用 OpenCL 来说很划算,因为有一个 OpenGL-OpenCL 互操作性 API (cl_khr_gl_sharing),允许与 OpenCL 函数共享 OpenGL 纹理数据而无需复制(当然有一些限制)。

让我们为我们的应用程序创建一个基础,该基础仅将 Android 相机配置为将预览帧发送到 OpenGL 纹理,并在显示器上显示这些帧,而无需进行任何处理。

用于此目的的最小类Activity如下所示:Activity

public class Tutorial4Activity extends Activity {
private MyGLSurfaceView mView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mView = new MyGLSurfaceView(this);
setContentView(mView);
}
@Override
protected void onPause() {
mView.onPause();
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
mView.onResume();
}
}

和最小的类View分别是

​
public class MyGLSurfaceView extends CameraGLSurfaceView implements CameraGLSurfaceView.CameraTextureListener {
static final String LOGTAG = "MyGLSurfaceView";
protected int procMode = NativePart.PROCESSING_MODE_NO_PROCESSING;
static final String[] procModeName = new String[] {"No Processing", "CPU", "OpenCL Direct", "OpenCL via OpenCV"};
protected int frameCounter;
protected long lastNanoTime;
TextView mFpsText = null;
public MyGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
if(e.getAction() == MotionEvent.ACTION_DOWN)
((Activity)getContext()).openOptionsMenu();
return true;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
super.surfaceCreated(holder);
//NativePart.initCL();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//NativePart.closeCL();
super.surfaceDestroyed(holder);
}
public void setProcessingMode(int newMode) {
if(newMode>=0 && newMode<procModeName.length)
procMode = newMode;
else
Log.e(LOGTAG, "Ignoring invalid processing mode: " + newMode);
((Activity) getContext()).runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(getContext(), "Selected mode: " + procModeName[procMode], Toast.LENGTH_LONG).show();
}
});
}
@Override
public void onCameraViewStarted(int width, int height) {
((Activity) getContext()).runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(getContext(), "onCameraViewStarted", Toast.LENGTH_SHORT).show();
}
});
if (NativePart.builtWithOpenCL())
NativePart.initCL();
frameCounter = 0;
lastNanoTime = System.nanoTime();
}
@Override
public void onCameraViewStopped() {
((Activity) getContext()).runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(getContext(), "onCameraViewStopped", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public boolean onCameraTexture(int texIn, int texOut, int width, int height) {
// FPS
frameCounter++;
if(frameCounter >= 30)
{
final int fps = (int) (frameCounter * 1e9 / (System.nanoTime() - lastNanoTime));
Log.i(LOGTAG, "drawFrame() FPS: "+fps);
if(mFpsText != null) {
Runnable fpsUpdater = new Runnable() {
public void run() {
mFpsText.setText("FPS: " + fps);
}
};
new Handler(Looper.getMainLooper()).post(fpsUpdater);
} else {
Log.d(LOGTAG, "mFpsText == null");
mFpsText = (TextView)((Activity) getContext()).findViewById(R.id.fps_text_view);
}
frameCounter = 0;
lastNanoTime = System.nanoTime();
}
if(procMode == NativePart.PROCESSING_MODE_NO_PROCESSING)
return false;
NativePart.processFrame(texIn, texOut, width, height, procMode);
return true;
}
}

注意

我们使用两个渲染器类:一个用于旧版 Camera API,另一个用于现代 Camera2。

一个最小的类Renderer可以在 Java 中实现(OpenGL ES 2.0 在 Java 中可用),但由于我们将使用 OpenCL 修改预览纹理,因此让我们将 OpenGL 的东西移动到 JNI。下面是 JNI 内容的简单 Java 包装器:

public class NativePart {
static
{
System.loadLibrary("opencv_java4");
System.loadLibrary("JNIpart");
}
public static final int PROCESSING_MODE_NO_PROCESSING = 0;
public static final int PROCESSING_MODE_CPU = 1;
public static final int PROCESSING_MODE_OCL_DIRECT = 2;
public static final int PROCESSING_MODE_OCL_OCV = 3;
public static native boolean builtWithOpenCL();
public static native int initCL();
public static native void closeCL();
public static native void processFrame(int tex1, int tex2, int w, int h, int mode);
}

由于 Camera 和Camera2  API 在相机设置和控制方面存在很大差异,因此让我们为两个相应的渲染器创建一个基类:

​
public abstract class MyGLRendererBase implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
protected final String LOGTAG = "MyGLRendererBase";
protected SurfaceTexture mSTex;
protected MyGLSurfaceView mView;
protected boolean mGLInit = false;
protected boolean mTexUpdate = false;
MyGLRendererBase(MyGLSurfaceView view) {
mView = view;
}
protected abstract void openCamera();
protected abstract void closeCamera();
protected abstract void setCameraPreviewSize(int width, int height);
public void onResume() {
Log.i(LOGTAG, "onResume");
}
public void onPause() {
Log.i(LOGTAG, "onPause");
mGLInit = false;
mTexUpdate = false;
closeCamera();
if(mSTex != null) {
mSTex.release();
mSTex = null;
NativeGLRenderer.closeGL();
}
}
@Override
public synchronized void onFrameAvailable(SurfaceTexture surfaceTexture) {
//Log.i(LOGTAG, "onFrameAvailable");
mTexUpdate = true;
mView.requestRender();
}
@Override
public void onDrawFrame(GL10 gl) {
//Log.i(LOGTAG, "onDrawFrame");
if (!mGLInit)
return;
synchronized (this) {
if (mTexUpdate) {
mSTex.updateTexImage();
mTexUpdate = false;
}
}
NativeGLRenderer.drawFrame();
}
@Override
public void onSurfaceChanged(GL10 gl, int surfaceWidth, int surfaceHeight) {
Log.i(LOGTAG, "onSurfaceChanged("+surfaceWidth+"x"+surfaceHeight+")");
NativeGLRenderer.changeSize(surfaceWidth, surfaceHeight);
setCameraPreviewSize(surfaceWidth, surfaceHeight);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.i(LOGTAG, "onSurfaceCreated");
String strGLVersion = GLES20.glGetString(GLES20.GL_VERSION);
if (strGLVersion != null)
Log.i(LOGTAG, "OpenGL ES version: " + strGLVersion);
int hTex = NativeGLRenderer.initGL();
mSTex = new SurfaceTexture(hTex);
mSTex.setOnFrameAvailableListener(this);
openCamera();
mGLInit = true;
}
}

如您所见, Camera 和 Camera2  APIs的继承者应实现以下抽象方法:

protected abstract void openCamera();
protected abstract void closeCamera();
protected abstract void setCameraPreviewSize(int width, int height);

让我们把它们实现的细节留给本教程之外,请参考源代码查看它们。

预览帧修改

OpenGL ES 2.0 初始化的细节也相当简单明了,这里要引用的嘈杂,但这里重要的一点是,作为相机预览目标的 OpeGL 纹理应该是类型(不是),在内部它以 YUV 格式保存图片数据。这使得无法通过 CL-GL 互操作 () 共享它并通过 C/C++ 代码访问其像素数据。为了克服这个限制,我们必须使用 FrameBuffer 对象(又名 FBO)执行从这个纹理到另一个常规纹理的 OpenGL 渲染

OpenGL ES 2.0 初始化的细节也相当简单明了,这里要引用的嘈杂,但这里重要的一点是,作为相机预览目标的 OpeGL 纹理应该是类型(GL_TEXTURE_EXTERNAL_OES不是GL_TEXTURE_2D),在内部它以 YUV 格式保存图片数据。这使得无法通过 CL-GL cl_khr_gl_sharing互操作 () 共享它并通过 C/C++ 代码访问其像素数据。为了克服这个限制,我们必须使用 FrameBuffer 对象(又名 FBO)执行从这个纹理GL_TEXTURE_2D到另一个常规纹理的 OpenGL 渲染。

C/C++ code

之后,我们可以从 C/C++ 读取( glReadPixels()复制)像素数据,并通过修改后将它们写回纹理 glTexSubImage2D()

直接 OpenCL 调用

此外,该纹理可以在不复制的情况下与 OpenCL 共享,但我们必须以特殊方式创建 OpenCL context如下:

​
int initCL()
{
dumpCLinfo();
LOGE("initCL: start initCL");
EGLDisplay mEglDisplay = eglGetCurrentDisplay();
if (mEglDisplay == EGL_NO_DISPLAY)
LOGE("initCL: eglGetCurrentDisplay() returned 'EGL_NO_DISPLAY', error = %x", eglGetError());
EGLContext mEglContext = eglGetCurrentContext();
if (mEglContext == EGL_NO_CONTEXT)
LOGE("initCL: eglGetCurrentContext() returned 'EGL_NO_CONTEXT', error = %x", eglGetError());
cl_context_properties props[] =
{ CL_GL_CONTEXT_KHR, (cl_context_properties) mEglContext,
CL_EGL_DISPLAY_KHR, (cl_context_properties) mEglDisplay,
CL_CONTEXT_PLATFORM, 0,
0 };
try
{
haveOpenCL = false;
cl::Platform p = cl::Platform::getDefault();
std::string ext = p.getInfo<CL_PLATFORM_EXTENSIONS>();
if(ext.find("cl_khr_gl_sharing") == std::string::npos)
LOGE("Warning: CL-GL sharing isn't supported by PLATFORM");
props[5] = (cl_context_properties) p();
theContext = cl::Context(CL_DEVICE_TYPE_GPU, props);
std::vector<cl::Device> devs = theContext.getInfo<CL_CONTEXT_DEVICES>();
LOGD("Context returned %d devices, taking the 1st one", devs.size());
ext = devs[0].getInfo<CL_DEVICE_EXTENSIONS>();
if(ext.find("cl_khr_gl_sharing") == std::string::npos)
LOGE("Warning: CL-GL sharing isn't supported by DEVICE");
theQueue = cl::CommandQueue(theContext, devs[0]);
cl::Program::Sources src(1, std::make_pair(oclProgI2I, sizeof(oclProgI2I)));
theProgI2I = cl::Program(theContext, src);
theProgI2I.build(devs);
cv::ocl::attachContext(p.getInfo<CL_PLATFORM_NAME>(), p(), theContext(), devs[0]());
if( cv::ocl::useOpenCL() )
LOGD("OpenCV+OpenCL works OK!");
else
LOGE("Can't init OpenCV with OpenCL TAPI");
haveOpenCL = true;
}
catch(const cl::Error& e){
LOGE("cl::Error: %s (%d)", e.what(), e.err());
return 1;
}
catch(const std::exception& e)
{
LOGE("std::exception: %s", e.what());
return 2;
}
catch(...)
{
LOGE( "OpenCL info: unknown error while initializing OpenCL stuff" );
return 3;
}
LOGD("initCL completed");
if (haveOpenCL)
return 0;
else
return 4;
}

然后,纹理可以被对象包装 cl::ImageGL并通过 OpenCL 调用进行处理

​cl::ImageGL imgIn (theContext, CL_MEM_READ_ONLY, GL_TEXTURE_2D, 0, texIn);
cl::ImageGL imgOut(theContext, CL_MEM_WRITE_ONLY, GL_TEXTURE_2D, 0, texOut);
std::vector < cl::Memory > images;
images.push_back(imgIn);
images.push_back(imgOut);
int64_t t = getTimeMs();
theQueue.enqueueAcquireGLObjects(&images);
theQueue.finish();
LOGD("enqueueAcquireGLObjects() costs %d ms", getTimeInterval(t));
t = getTimeMs();
cl::Kernel Laplacian(theProgI2I, "Laplacian"); //TODO: may be done once
Laplacian.setArg(0, imgIn);
Laplacian.setArg(1, imgOut);
theQueue.finish();
LOGD("Kernel() costs %d ms", getTimeInterval(t));
t = getTimeMs();
theQueue.enqueueNDRangeKernel(Laplacian, cl::NullRange, cl::NDRange(w, h), cl::NullRange);
theQueue.finish();
LOGD("enqueueNDRangeKernel() costs %d ms", getTimeInterval(t));
t = getTimeMs();
theQueue.enqueueReleaseGLObjects(&images);
theQueue.finish();
LOGD("enqueueReleaseGLObjects() costs %d ms", getTimeInterval(t));

OpenCV T-API

但是,与其自己编写 OpenCL 代码,不如使用隐式调用 OpenCL 的 OpenCV T-API。您只需要将创建的 OpenCL 上下文传递给 OpenCV(通过cv::ocl::attachContext() ),并以某种  cv::UMat.方式将 OpenGL 纹理包装起来。不幸的是,OpenCL 缓冲区在内部保留,它不能包装在 OpenGL 纹理或 OpenCL 图像上 - 因此我们必须在此处复制图像数据:

​int64_t t = getTimeMs();
cl::ImageGL imgIn (theContext, CL_MEM_READ_ONLY, GL_TEXTURE_2D, 0, texIn);
std::vector < cl::Memory > images(1, imgIn);
theQueue.enqueueAcquireGLObjects(&images);
theQueue.finish();
cv::UMat uIn, uOut, uTmp;
cv::ocl::convertFromImage(imgIn(), uIn);
LOGD("loading texture data to OpenCV UMat costs %d ms", getTimeInterval(t));
theQueue.enqueueReleaseGLObjects(&images);
t = getTimeMs();
//cv::blur(uIn, uOut, cv::Size(5, 5));
cv::Laplacian(uIn, uTmp, CV_8U);
cv:multiply(uTmp, 10, uOut);
cv::ocl::finish();
LOGD("OpenCV processing costs %d ms", getTimeInterval(t));
t = getTimeMs();
cl::ImageGL imgOut(theContext, CL_MEM_WRITE_ONLY, GL_TEXTURE_2D, 0, texOut);
images.clear();
images.push_back(imgOut);
theQueue.enqueueAcquireGLObjects(&images);
cl_mem clBuffer = (cl_mem)uOut.handle(cv::ACCESS_READ);
cl_command_queue q = (cl_command_queue)cv::ocl::Queue::getDefault().ptr();
size_t offset = 0;
size_t origin[3] = { 0, 0, 0 };
size_t region[3] = { (size_t)w, (size_t)h, 1 };
CV_Assert(clEnqueueCopyBufferToImage (q, clBuffer, imgOut(), offset, origin, region, 0, NULL, NULL) == CL_SUCCESS);
theQueue.enqueueReleaseGLObjects(&images);
cv::ocl::finish();
LOGD("uploading results to texture costs %d ms", getTimeInterval(t));

注意

当通过 OpenCL 图像包装器将修改后的图像放回原始 OpenGL 纹理时,我们必须再制作一个图像数据副本。

性能说明

为了比较在具有720p相机分辨率的Sony Xperia Z3上,通过C / C++代码(调用cv::Laplacian与cv::Mat),直接OpenCL调用(使用OpenCL图像进行输入和输出)和OpenCV T-API(调用cv::Laplacian与cv::UMat)完成的相同预览帧修改(Laplacian)的FPS:

  • C/C++ 版本显示 3-4 fps
  • 直接 OpenCL 调用显示 25-27 fps
  • OpenCV T-API 显示 11-13 fps(由于额外的来回复制)cl_imagecl_buffer

参考文献:

1、《Use OpenCL in Android camera preview based CV application》  Andrey Pavlenko, Alexander Panov

相关文章:

在基于Android相机预览的CV应用程序中使用 OpenCL

查看&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV4.9.0在Android 开发简介 下一篇&#xff1a;在 MacOS 中安装 本指南旨在帮助您在基于 Android 相机预览的 CV 应用程序中使用 OpenCL ™。教程是为 Android Studio 20…...

网络分类简述与数据链路层协议(PPP)

实验拓扑 实验要求 1、R1和R2使用PPP链路直连&#xff0c;R2和R3把2条PPP链路捆绑为PPP MP直连按照图示配置IP地址 2、R2对R1的PPP进行单向chap验证 3、R2和R3的PPP进行双向chap验证 实验思路 给R1、R2的S3/0/0接口配置IP地址&#xff0c;已给出网段192.168.1.0/24R2作为主…...

Linux文件系列:磁盘,文件系统,软硬链接

Linux文件系列:磁盘,文件系统,软硬链接 一.磁盘相关知识1.磁盘机械构成2.磁盘物理存储3.磁盘逻辑存储1.LBA地址2.磁盘的分区和分组 二.文件系统和inode1.inode结构体2.文件系统1.Super Block(超级块)2.Group Descriptor Table(块组描述表GDT)3.inode Table4.Data Blocks5.Block…...

GPT4.0

GPT4.0 支持官网所有功能以及所有第三方GPTS&#xff0c;完全同步官网。无需魔法&#xff0c;填写授权码直达官网。全天超18小时维护&#xff0c;无需担心不稳定。没有永久卡&#xff0c;3.5免费提供&#xff0c;4.0可以按需下单即可&#xff0c;不存在跑路。 需要的联系...

软件工程(双语)

教材《软件工程 实践者的研究方法》 双语教学&#xff0c;但目前感觉都是在讲没用的 ”过程决定质量&#xff0c;复用决定效率” 介绍 软工的本质 程序数据结构算法 软件程序文档&#xff08;需求、模型、说明书&#xff09; 软件应用&#xff1a; 系统软件 应用 工程/科学…...

网络——套接字编程UDP

目录 端口号 源端口号和目的端口号 认识TCP协议和UDP协议 网络字节序 socket编程接口 socket常见接口 sockaddr结构 UDP socket bind recvfrom sendto 编写客户端 绑定INADDR_ANY 实现聊天功能 端口号 在这之前我们已经说过源IP地址和目的IP地址&#xff0c;还有…...

FPGA_AD9361

1.集成12位DAC和ADC的一款器件&#xff0c;2个输入模拟通道和2个输出模拟通道 2.• TX频段&#xff1a;47 MHz至6.0 GHz • RX频段&#xff1a;70 MHz至6.0 GHz 3.SPI配置成LVDS或CMOS接口&#xff0c;也可以还可以选择FDD&#xff08;频分双工——全双工&#xff0c;操作时需…...

探讨Java代码混淆加固工具

摘要 本篇博客将介绍几种常用的Java代码混淆工具&#xff0c;如ProGuard、Allatori Java Obfuscator、VirboxProtector、ipaguard和DashO。我们将深入探讨它们的特点、功能以及在保护Java应用程序安全方面的作用。此外&#xff0c;还将强调在使用Java代码混淆工具时需要注意的安…...

Linux——du, df命令查看磁盘空间使用情况

一、实现原理&#xff1a; df 命令的全称是Disk Free &#xff0c;显而易见它是统计磁盘中空闲的空间&#xff0c;也即空闲的磁盘块数。它是通过文件系统磁盘块分配图进行计算出的。 du 命令的全称是 Disk Used &#xff0c;统计磁盘有已经使用的空间。它是直接统计各文件各目…...

数据库实验(一)SQL Server触发器

目录 触发器的定义 触发器和存储过程的区别 触发器的优点 触发器的作用 触发器的分类 DML触发器 DDL触发器 登录触发器 触发器的工作原理 inserted表 deleted表 创建触发器 编程要求 测试要求&#xff1a; 实验代码&#xff1a; 触发器的定义 触发器是建立在触…...

添加网址到主页

基于localStorage的网址收藏夹-CSDN博客 为了通过安卓菜单添加网址到主页中&#xff0c;调试了几个小时&#xff0c;主要踩了几个坑。 1.localStorage 通过域名隔离&#xff0c;需要加载主页才能读写。 2.WebView 可以不显示&#xff0c;但是 JS 代码要放在 window.onload 中…...

消息中间件如何实现高可用

消息中间件实现高可用的方式有很多种&#xff0c;常见的方法包括&#xff1a; 集群部署&#xff1a;通过在多台服务器上部署消息中间件实例&#xff0c;构成一个集群&#xff0c;提高整体系统的可用性。当一台机器出现故障时&#xff0c;其他机器可以继续提供服务。主从复制&a…...

Hbase 王者荣耀数据表 HBase常用Shell命令

大数据课本&#xff1a; HBase常用Shell命令 在使用具体的Shell命令操作HBase数据之前&#xff0c;需要首先启动Hadoop&#xff0c;然后再启动HBase&#xff0c;并且启动HBase Shell&#xff0c;进入Shell命令提示符状态&#xff0c;具体命令如下&#xff1a; $ cd /usr/local…...

家用智能洗地机哪个牌子好?4款型号让你解锁高效省力生活体验

在今天的社会中&#xff0c;随着生活节奏的加快&#xff0c;人们对于家庭清洁的需求不断增加。传统的清洁方法已经无法满足现代家庭的需求。因此&#xff0c;洗地机作为一种高效、方便的清洁工具&#xff0c;已经成为了许多家庭首选的清洁设备。然而&#xff0c;在市场上&#…...

Linux--进程(1)

目录 前言 1.冯诺依曼体系结构 2. 操作系统(Operator System)--第一个被加载的软件 3.进程 3.1基本概念 3.2Linux中的PCB 3.3通过系统调用创建子进程-fork初识 fork&#xff1a;创建一个子进程 为什么要创建子进程&#xff1f; fork的原理&#xff1a; 进一步了解fo…...

Qt登录页面

#include "mywidget.h" #include "ui_mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent), ui(new Ui::MyWidget) {ui->setupUi(this);//接收动图QMovie *mv new QMovie(":/pictrue/luori.gif");ui->loglab->setMovie(…...

软件工程-第8章 软件测试

8.1 软件测试目标域软件测试过程模型 8.2 软件测试技术 8.3 静态分析技术-程序正确性证明 8.4 软件测试步骤 8.5 本章小结...

专业135+总分400+重庆邮电大学801信号与系统考研经验重邮电子信息与通信工程,真题,大纲,参考书。

今年分数出来还是比较满意&#xff0c;专业801信号与系统135&#xff0c;总分400&#xff0c;没想到自己也可以考出400以上的分数&#xff0c;一年的努力付出都是值得的&#xff0c;总结一下自己的复习心得&#xff0c;希望对大家复习有所帮助。专业课&#xff1a;&#xff08;…...

主干网络篇 | YOLOv8改进之在主干网络中引入密集连接卷积网络DenseNet

前言:Hello大家好,我是小哥谈。DenseNet(密集连接卷积网络)是一种深度学习神经网络架构,它在2017年由Gao Huang等人提出。DenseNet的核心思想是通过密集连接(dense connection)来促进信息的流动和共享。在传统的卷积神经网络中,每个层的输入只来自于前一层的输出。而在…...

lavarel的php程序是顺序执行,用pdo mysql连接池好像没有什么用啊。没有办法挂起等待啊,为什么要用连接池,应用场景是什么

Laravel 的 PHP 程序确实是基于请求-响应模式&#xff0c;每个请求都是顺序执行的。这意味着一旦一个请求开始处理&#xff0c;它会按照代码的顺序执行&#xff0c;直到完成并返回响应。因此&#xff0c;从表面上看&#xff0c;使用 PDO 或 MySQL 连接池在 Laravel 中可能看起来…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

1.3 VSCode安装与环境配置

进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件&#xff0c;然后打开终端&#xff0c;进入下载文件夹&#xff0c;键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

ip子接口配置及删除

配置永久生效的子接口&#xff0c;2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...