在基于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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

复习斐波那契(用C++写)

或者这样写&#xff1a; 斐波那契数列 题目描述 斐波那契数列是指这样的数列&#xff1a;数列的第一个和第二个数都为 1 1 1&#xff0c;接下来每个数都等于前面 2 2 2 个数之和。 给出一个正整数 a a a&#xff0c;要求斐波那契数列中第 a a a 个数是多少。 输入格式…

霍格沃兹测试开发从人员外包到测试工具、测试平台,提供全方位的测试解决方案~

随着学社的学员越来越多&#xff0c;影响力越来越大&#xff0c;不停有学员和企业问我们&#xff1a;能否提供人员外包服务&#xff1f;与此同时&#xff0c;企业对于外包人员的业务技能要求也越来越高&#xff0c;寻找一个稳定靠谱的供应商也成了很多学员所在公司的需求。对此…

SCI一区 | Matlab实现SSA-TCN-BiGRU-Attention麻雀算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现SSA-TCN-BiGRU-Attention麻雀算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现SSA-TCN-BiGRU-Attention麻雀算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型描述程序…

树莓派夜视摄像头拍摄红外LED灯

NoIR相机是一种特殊类型的红外摄像头&#xff0c;其名称来源于"No Infrared"的缩写。与普通的彩色摄像头不同&#xff0c;NoIR相机具备红外摄影和低光条件下摄影的能力。 一般摄像头能够感知可见光&#xff0c;并用于普通摄影和视频拍摄。而NoIR相机则在设计上去除了…

基于python+vue共享单车信息系统的设计与实现flask-django-php-nodejs

课题主要分为二大模块&#xff1a;即管理员模块和用户模块&#xff0c;主要功能包括&#xff1a;用户、区域、共享单车、单车租赁、租赁归还、报修信息、检修信息等&#xff1b;快速发展的社会中&#xff0c;人们的生活水平都在提高&#xff0c;生活节奏也在逐渐加快。为了节省…

安卓实现翻转时间显示效果

效果 废话不多说上代码 自定义组件 import android.content.Context; import android.content.res.TypedArray; import android.graphics.Camera; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.…

谷歌具身智能最新进展:RT-H 机器人通用灵巧抓取

随着 GPT-4 等大型语言模型与机器人研究的结合愈发紧密&#xff0c;人工智能正在越来越多地走向现实世界&#xff0c;因此具身智能相关的研究也正受到越来越多的关注。在众多研究项目中&#xff0c;谷歌的「RT」系列机器人始终走在前沿&#xff08;参见《大模型正在重构机器人&…

实现React组件之间的数据共享:使用React Redux

实现React组件之间的数据共享&#xff1a;使用React Redux 在复杂的React应用中&#xff0c;组件之间的数据共享是必不可少的。为了解决这个问题&#xff0c;可以使用React Redux来管理应用的状态&#xff0c;并实现组件之间的数据共享。在本文中&#xff0c;我们将介绍如何使…

修改网站源码,给电子商城的商品添加图片时商品id为0的原因

修改网站源码&#xff0c;给电子商城的商品添加图片时商品id为0的原因。花了几个小时查找原因。后来&#xff0c;由于PictureControl.class.php是复制CourseControl.class.php而来&#xff0c;于是对比了这两个文件&#xff0c;在CourseControl.class.php找到了不一样的关键几条…

Svg Flow Editor 原生svg流程图编辑器(三)

系列文章 Svg Flow Editor 原生svg流程图编辑器&#xff08;一&#xff09; Svg Flow Editor 原生svg流程图编辑器&#xff08;二&#xff09; Svg Flow Editor 原生svg流程图编辑器&#xff08;三&#xff09; 实现对齐辅助线 在 logicFlow 中&#xff0c;辅助线的实现是通…

SpringCloudGateway之高性能篇

SpringCloudGateway之高性能篇 背景 在公司的开放平台中&#xff0c;为了统一管理对外提供的接口、处理公共逻辑、实现安全防护及流量控制&#xff0c;确实需要一个API网关作为中间层。 场景 统一接入点: API网关作为所有对外服务的单一入口&#xff0c;简化客户端对内部系统…

基于python+vue渔船出海及海货统计系统的设计与实现flask-django-php-nodejs

当今社会已经步入了科学技术进步和经济社会快速发展的新时期&#xff0c;国际信息和学术交流也不断加强&#xff0c;计算机技术对经济社会发展和人民生活改善的影响也日益突出&#xff0c;人类的生存和思考方式也产生了变化。传统渔船出海及海货统计采取了人工的管理方法&#…

2024.3.22 使用nginx在window下运行前端页面

2024.3.22 使用nginx在window下运行前端页面 使用nginx可以在本地运行前端程序&#xff0c;解决本地前后端程序跨域问题&#xff0c;是个前期编程及测试的好办法。 nginx下载 直接在官网下载 本次选择了1.24版本&#xff08;stable version&#xff09; nginx安装 解压后…

低压MOS在无人机上的应用-REASUNOS瑞森半导体

一、前言 无人机的结构由机身、动力系统、飞行控制系统、链路系统、任务载荷等几个方面组成的。 无人机动力系统中的电机&#xff0c;俗称“马达”&#xff0c;是无人机的动力来源&#xff0c;无人机通过改变电机的转速来改变无人机的飞行状态。即改变每个电机的速度&#xf…

vue+element 前端实现增删查改+分页,不调用后端

前端实现增删查改分页&#xff0c;不调用后端。 大概就是对数组内的数据进行增删查改分页 没调什么样式&#xff0c;不想写后端&#xff0c;当做练习 <template><div><!-- 查询 --><el-form :inline"true" :model"formQuery">&l…

牛客NC403 编辑距离为一【中等 模拟法 Java,Go,PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/0b4b22ae020247ba8ac086674f1bd2bc 思路 注意&#xff1a;必须要新增一个&#xff0c;或者删除一个&#xff0c;或者替换一个&#xff0c;所以不能相等1.如果s和t相等&#xff0c;返回false,如果s和t长度差大于1…

全栈的自我修养 ———— uniapp中加密方法

直接按部就班一步一步来 一、首先创建一个js文件填入AES二、创建加密解密方法三、测试 一、首先创建一个js文件填入AES 直接复制以下内容 /* CryptoJS v3.1.2 code.google.com/p/crypto-js (c) 2009-2013 by Jeff Mott. All rights reserved. code.google.com/p/crypto-js/wi…

HTML_CSS学习:表格、表单、框架标签

一、表格_跨行与跨列 1.相关代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>表格_跨行与跨列</title> </head> <body><table border"1" cellspacing"0&qu…

5 线程网格、线程块以及线程(1)

5.1 简介 英伟达为它的硬件调度方式选择了一种比较有趣的模型&#xff0c;即SPMD(单程序多数据Single Program&#xff0c;Multiple Data)&#xff0c;属于SIMD(单指令多数据)的一种变体。从某些方面来说&#xff0c;这种调度方式的选择是基于英伟达自身底层硬件的实现。并行编…

GPT-5可能会在今年夏天作为对ChatGPT的“实质性改进”而到来

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…