Android实现戴口罩人脸检测和戴口罩识别(附Android源码)

news2025/4/19 9:52:43

Android实现戴口罩人脸检测和戴口罩识别(附Android源码)

目录

Android实现戴口罩人脸检测和戴口罩识别(附Android源码)

1.戴口罩识别的方法

(1)基于多类别目标检测的戴口罩识别方法

(2)基于人脸检测+戴口罩分类识别方法

2.戴口罩人脸数据集

3.戴口罩人脸检测

4.戴口罩识别模型训练

5.戴口罩识别模型Android部署

(1) 将Pytorch模型转换ONNX模型

(2) 将ONNX模型转换为TNN模型

(3) Android端上部署戴口罩识别

(4) Android测试效果 

(5) 运行APP闪退:dlopen failed: library "libomp.so" not found

6.项目源码下载


本篇博文是《Pytorch戴口罩人脸检测和戴口罩识别(含训练代码 戴口罩人脸数据集)》续作Android篇,主要分享将Python训练后的戴口罩识别模型移植到Android平台。我们将开发一个简易的、可实时运行的戴口罩人脸检测和戴口罩识别的Android Demo。目前项目开发的戴口罩识别(face-mask recognition)的准确率还挺高的,在resnet50,可以高达99%的准确率,采用轻量化版本MobileNet-v2,准确率也可以高达98.18%左右。

项目将手把手教你将训练好的戴口罩分类识别模型部署到Android平台中,包括如何转为ONNX,TNN模型,并移植到Android上进行部署,实现一个戴口罩识别的Android Demo APP 。APP在普通Android手机上可以达到实时的检测识别效果,CPU(4线程)约30ms左右,GPU约25ms左右 ,基本满足业务的性能需求。

【尊重原创,转载请注明出处】https://blog.csdn.net/guyuealian/article/details/128404379

先展示一下Android版本戴口罩识别Demo效果: 

图片测试视频测试

Android项目源码下载地址:Android实现戴口罩人脸检测和戴口罩识别(附Android源码)

整套Android项目,包含的资源内容主要有:

  1. 提供Android版本的人脸检测(支持戴口罩人脸检测)
  2. 提供戴口罩识别Android Demo源码
  3. Android Demo在普通手机CPU/GPU上可以实时检测和识别,约30ms左右
  4. Android Demo支持图片,视频,摄像头测试

戴口罩人脸检测和戴口罩识 别Android Demo APP体检: https://pan.baidu.com/s/1meGv_J6xZiDvXzvXBzNnHA 提取码: 73e5 或者戴口罩人脸检测和戴口罩识别AndroidDemoAPP_口罩识别论文及源代码-Android文档类资源-CSDN下载


1.戴口罩识别的方法

(1)基于多类别目标检测的戴口罩识别方法

基于多类别目标检测的戴口罩识别方法,一步到位,把未戴口罩(nomask)和戴口罩(mask)两个类别直接当成两个目标检测的类别进行训练

  • 优点:直接端到端训练,任务简单,速度快
  • 缺点:需要人工标注人脸框mask和nomask,时间花费比较大;训练数据不足的情况下,容易出现误检测的情况

(2)基于人脸检测+戴口罩分类识别方法

该方法,先采用通用的人脸检测模型,进行人脸检测,然后裁剪人脸区域,再训练一个戴口罩分类器,对人脸进行分类识别(未戴口罩和戴口罩)

  • 优点:不需要标注人脸框数据,可以自己合成戴口罩人脸数据,人工成本低;精度高,可针对分类模型进行轻量化
  • 缺点:需要部署两个模型(人脸检测模型和戴口罩分类模型),人脸越多,速度越慢

考虑到数据标注成本的问题,本项目采用第二种方法,即采用基于人脸检测+戴口罩分类识别方法


2.戴口罩人脸数据集

网上绝大部分人脸数据都是不戴口罩的人脸,不能直接用于戴口罩识别中。鉴于此,我们可以考虑自己合成/生成戴口罩的人脸数据,以下是鄙人收藏和整理的戴口罩人脸数据集和合成的数据集,总共约有50000+的数据:

原始图片生成戴口罩人脸

 关于戴口罩人脸数据和生成方法,详细使用说明请参考我的一篇博客《戴口罩人脸数据集和戴口罩人脸生成方法》

数据集说明
facemask-train1
  • 从网上收集的戴口罩人脸数据集(如virus-mask-dataset),约7000+张图片,并清洗了部分标注错误的样本
  • 每张图片都被标注了mask(戴口罩)和nomask(未佩戴口罩)的检测框
  • 标注格式为标准的VOC xml格式,可用于人脸检测训练数据使用
  • 已经裁剪了人脸区域,并清洗了部分标注错误的样本;其中mask(戴口罩)人脸有3000+张,nomask(未佩戴口罩)人脸有10000+张,可作为分类训练数据集,
facemask-train2
  • 从网上收集的戴口罩人脸数据集,约3500+张图片,
  • 每张图片都被标注了mask(戴口罩)和nomask(未佩戴口罩)的检测框
  • 标注格式为标准的VOC格式,但标注的人脸框比较大,不建议用于人脸检测训练数据使用
  • 已经裁剪了人脸区域图像,并清洗了部分标注错误的样本;其中mask(戴口罩)人脸有2000+张,nomask(未佩戴口罩)人脸有6000+张,可作为分类训练数据集
facemask-train3
  • 从网上收集的戴口罩人脸数据集,其中mask(戴口罩)人脸有600+张,nomask(未佩戴口罩)人脸有1700+张,可作为分类训练数据集
  • 原始图片都被裁剪为人脸图像了,所以不合适用于人脸检测;可作为分类训练数据集
synthetic-train1
  • 这是合成的戴口罩人脸数据
  • 其中mask(戴口罩)人脸有7000+张,nomask(未佩戴口罩)人脸有7000+张,可作为分类训练数据集
synthetic-train2
  • 这是合成的戴口罩人脸数据
  • 其中mask(戴口罩)人脸有6000+张,nomask(未佩戴口罩)人脸有6000+张,可作为分类训练数据集
facemask-test
  • 这是戴口罩人脸测试集
  • 其中mask(戴口罩)人脸有300+张,nomask(未佩戴口罩)人脸有300+张,用于分类模型测试

3.戴口罩人脸检测

通常我们理解的人脸检测是指没有遮挡或者只有少许遮挡情况下的人脸检测,当人脸戴有口罩,其检测效果势必会变得比较差,而大量标注带有人脸口罩的人脸数据集还是比较耗时费力的。所以我的方法是:

先在WiderFace人脸数据集上,训练人脸检测;然后在facemask-train1数据集finetune人脸检测模型,经过这个方法训练后,其戴口罩检测效果会好很多。

当然,即使使用开源的人脸检测算法,在带有口罩人脸检测,其实效果也不会太差,比如使用FaceBox,MTCNN检测带有口罩的图片,效果也可以的,只不过会经常出现人脸检测框不完整,存在缺少等问题,对后续的戴口罩的识别有一定的影响。

关于人脸检测的方法,可以参考我的另一篇博客:行人检测和人脸检测和人脸关键点检测(C++/Android源码)_AI吃大瓜的博客-CSDN博客_android 人体姿态识别源码考虑到人脸人体检测的需求,本人开发了一套轻量化的,高精度的,可实时的人脸/人体检测Android Demo,主要支持功能如下:支持人脸检测算法模型支持人脸检测和人脸关键点检测(5个人脸关键点)算法模型支持人体检测(行人检测)算法模型支持人脸和人体同时检测算法模型所有算法模型都使用C++开发,推理框架采用TNN,Android通过JNI接口进行算法调用;所有算法模型都可在普通Android手机实时跑,在普通Android手机,CPU和GPU都可以达到实时检测的效果(CPU约25毫秒左右,GPU约1https://panjinquan.blog.csdn.net/article/details/125348189


4.戴口罩识别模型训练

关于训练戴口罩识别模型的方法,请参考本人另一篇博文《Pytorch戴口罩人脸检测和戴口罩识别(含训练代码 戴口罩人脸数据集)》


5.戴口罩识别模型Android部署

(1) 将Pytorch模型转换ONNX模型

训练好Pytorch模型后,你可以将模型转换为ONNX模型,方便后续模型部署

python libs/convert/convert_torch_to_onnx.py
"""
This code is used to convert the pytorch model into an onnx format model.
"""
import sys
import os

sys.path.insert(0, os.getcwd())
import torch.onnx
import onnx
from classifier.models.build_models import get_models
from basetrainer.utils import torch_tools


def build_net(model_file, net_type, input_size, num_classes, width_mult=1.0):
    """
    :param model_file: 模型文件
    :param net_type: 模型名称
    :param input_size: 模型输入大小
    :param num_classes: 类别数
    :param width_mult:
    :return:
    """
    model = get_models(net_type, input_size, num_classes, width_mult=width_mult, is_train=False, pretrained=False)
    state_dict = torch_tools.load_state_dict(model_file)
    model.load_state_dict(state_dict)
    return model


def convert2onnx(model_file, net_type, input_size, num_classes, width_mult=1.0, device="cpu", onnx_type="default"):
    model = build_net(model_file, net_type, input_size, num_classes, width_mult=width_mult)
    model = model.to(device)
    model.eval()
    model_name = os.path.basename(model_file)[:-len(".pth")] + ".onnx"
    onnx_path = os.path.join(os.path.dirname(model_file), model_name)
    # dummy_input = torch.randn(1, 3, 240, 320).to("cuda")
    dummy_input = torch.randn(1, 3, input_size[1], input_size[0]).to(device)
    # torch.onnx.export(model, dummy_input, onnx_path, verbose=False,
    #                   input_names=['input'],output_names=['scores', 'boxes'])
    do_constant_folding = True
    if onnx_type == "default":
        torch.onnx.export(model, dummy_input, onnx_path, verbose=False, export_params=True,
                          do_constant_folding=do_constant_folding,
                          input_names=['input'],
                          output_names=['output'])
    elif onnx_type == "det":
        torch.onnx.export(model,
                          dummy_input,
                          onnx_path,
                          do_constant_folding=do_constant_folding,
                          export_params=True,
                          verbose=False,
                          input_names=['input'],
                          output_names=['scores', 'boxes', 'ldmks'])
    elif onnx_type == "kp":
        torch.onnx.export(model,
                          dummy_input,
                          onnx_path,
                          do_constant_folding=do_constant_folding,
                          export_params=True,
                          verbose=False,
                          input_names=['input'],
                          output_names=['output'])
    onnx_model = onnx.load(onnx_path)
    onnx.checker.check_model(onnx_model)
    print(onnx_path)


if __name__ == "__main__":
    net_type = "mobilenet_v2"
    width_mult = 1.0
    input_size = [128, 128]
    num_classes = 2
    model_file = "work_space/mobilenet_v2_1.0_CrossEntropyLoss/model/best_model_022_98.1848.pth"
    convert2onnx(model_file, net_type, input_size, num_classes, width_mult=width_mult)

(2) 将ONNX模型转换为TNN模型

目前CNN模型有多种部署方式,可以采用TNN,MNN,NCNN,以及TensorRT等部署工具,鄙人采用TNN进行Android端上部署:

  • (1)将ONNX模型转换为TNN模型,请参考TNN官方说明:TNN/onnx2tnn.md at master · Tencent/TNN · GitHub
  • (2)一键转换,懒人必备:一键转换 Caffe, ONNX, TensorFlow 到 NCNN, MNN, Tengine   (版本问题,这个工具转换的TNN模型可能不兼容,建议还是自己build源码进行转换)

(3) Android端上部署戴口罩识别

项目实现了Android版本的戴口罩识别Demo,部署框架采用TNN,支持多线程CPU和GPU加速推理,在普通手机上可以实时处理。戴口罩识别Android源码,核心算法均采用C++实现,上层通过JNI接口调用.

如果你想在这个Android Demo部署你自己训练的分类模型,你可将训练好的Pytorch模型转换ONNX ,再转换成TNN模型,然后把TNN模型代替你模型即可。

  • 戴口罩人脸检测和戴口罩识别JNI接口 ,Java部分
package com.cv.tnn.model;

import android.graphics.Bitmap;

public class Detector {

    static {
        System.loadLibrary("tnn_wrapper");
    }


    /***
     * 初始化人脸检测和戴口罩识别模型
     * @param face_model: 人脸检测模型(不含后缀名)
     * @param class_model:戴口罩识别模型(不含后缀名)
     * @param root:模型文件的根目录,放在assets文件夹下
     * @param model_type:模型类型
     * @param num_thread:开启线程数
     * @param useGPU:关键点的置信度,小于值的坐标会置-1
     */
    public static native void init(String face_model, String class_model, String root, int model_type, int num_thread, boolean useGPU);

    /***
     * 人脸检测和戴口罩识别
     * @param bitmap 图像(bitmap),ARGB_8888格式
     * @param score_thresh:置信度阈值
     * @param iou_thresh:  IOU阈值
     * @return
     */
    public static native FrameInfo[] detect(Bitmap bitmap, float score_thresh, float iou_thresh);
}
  • 戴口罩人脸检测和戴口罩识别JNI接口 ,C++部分
#include <jni.h>
#include <string>
#include <fstream>
#include "src/object_detection.h"
#include "src/classification.h"
#include "src/Types.h"
#include "debug.h"
#include "android_utils.h"
#include "opencv2/opencv.hpp"
#include "file_utils.h"

using namespace dm;
using namespace vision;

static ObjectDetection *detector = nullptr;
static Classification *classifier = nullptr;

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    return JNI_VERSION_1_6;
}

JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) {

}


extern "C"
JNIEXPORT void JNICALL
Java_com_cv_tnn_model_Detector_init(JNIEnv *env,
                                    jclass clazz,
                                    jstring face_model,
                                    jstring class_model,
                                    jstring root,
                                    jint model_type,
                                    jint num_thread,
                                    jboolean use_gpu) {
    if (detector != nullptr) {
        delete detector;
        detector = nullptr;
    }
    std::string parent = env->GetStringUTFChars(root, 0);
    std::string face_model_ = env->GetStringUTFChars(face_model, 0);
    std::string class_model_ = env->GetStringUTFChars(class_model, 0);
    string face_model_file = path_joint(parent, face_model_ + ".tnnmodel");
    string face_proto_file = path_joint(parent, face_model_ + ".tnnproto");
    string class_model_file = path_joint(parent, class_model_ + ".tnnmodel");
    string class_proto_file = path_joint(parent, class_model_ + ".tnnproto");
    DeviceType device = use_gpu ? GPU : CPU;
    LOGW("parent     : %s", parent.c_str());
    LOGW("useGPU     : %d", use_gpu);
    LOGW("device_type: %d", device);
    LOGW("model_type : %d", model_type);
    LOGW("num_thread : %d", num_thread);
    ObjectDetectionParam model_param = FACE_MODEL;
    detector = new ObjectDetection(face_model_file,
                                   face_proto_file,
                                   model_param,
                                   num_thread,
                                   device);

    ClassificationParam ClassParam = FACE_MASK_MODEL;
    ClassParam.input_width = 112;
    ClassParam.input_height = 112;
    ClassParam.use_rgb = true; //
    classifier = new Classification(class_model_file,
                                    class_proto_file,
                                    ClassParam,
                                    num_thread,
                                    device);
}

extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_cv_tnn_model_Detector_detect(JNIEnv *env, jclass clazz, jobject bitmap,jfloat score_thresh, jfloat iou_thresh) {
    cv::Mat bgr;
    BitmapToMatrix(env, bitmap, bgr);
    int src_h = bgr.rows;
    int src_w = bgr.cols;
    // 检测区域为整张图片的大小
    FrameInfo resultInfo;
    // 开始检测
    if (detector != nullptr) {
        detector->detect(bgr, &resultInfo, score_thresh, iou_thresh);
    } else {
        ObjectInfo objectInfo;
        objectInfo.x1 = 0;
        objectInfo.y1 = 0;
        objectInfo.x2 = 84;
        objectInfo.y2 = 84;
        objectInfo.label = 0;
        resultInfo.info.push_back(objectInfo);
    }

    int nums = resultInfo.info.size();
    LOGW("object nums: %d\n", nums);
    if (nums > 0) {
        // 开始检测
        classifier->detect(bgr, &resultInfo);
        // 可视化代码
        printf("sitting label:%d,score:%3.5f", resultInfo.label, resultInfo.score);
        //classifier->visualizeResult(bgr, &resultInfo);
    }
    //cv::cvtColor(bgr, bgr, cv::COLOR_BGR2RGB);
    //MatrixToBitmap(env, bgr, dst_bitmap);
    auto BoxInfo = env->FindClass("com/cv/tnn/model/FrameInfo");
    auto init_id = env->GetMethodID(BoxInfo, "<init>", "()V");
    auto box_id = env->GetMethodID(BoxInfo, "addBox", "(FFFFIF)V");
    auto ky_id = env->GetMethodID(BoxInfo, "addKeyPoint", "(FFF)V");
    jobjectArray ret = env->NewObjectArray(resultInfo.info.size(), BoxInfo, nullptr);
    for (int i = 0; i < nums; ++i) {
        auto info = resultInfo.info[i];
        env->PushLocalFrame(1);
        //jobject obj = env->AllocObject(BoxInfo);
        jobject obj = env->NewObject(BoxInfo, init_id);
        // set bbox
        //LOGW("rect:[%f,%f,%f,%f] label:%d,score:%f \n", info.rect.x,info.rect.y, info.rect.w, info.rect.h, 0, 1.0f);
        env->CallVoidMethod(obj, box_id, info.x1, info.y1, info.x2 - info.x1, info.y2 - info.y1,
                            info.category.label, info.category.score);
        // set keypoint
        for (const auto &kps : info.landmarks) {
            //LOGW("point:[%f,%f] score:%f \n", lm.point.x, lm.point.y, lm.score);
            env->CallVoidMethod(obj, ky_id, (float) kps.x, (float) kps.y, 1.0f);
        }
        obj = env->PopLocalFrame(obj);
        env->SetObjectArrayElement(ret, i, obj);
    }
    return ret;
}

(4) Android测试效果 

Android Demo在普通手机CPU/GPU上可以达到实时检测和识别效果;CPU(4线程)约30ms左右,GPU约25ms左右 ,基本满足业务的性能需求。

戴口罩人脸检测和戴口罩识别Android Demo APP体检:https://pan.baidu.com/s/1meGv_J6xZiDvXzvXBzNnHA 提取码: 73e5 或者戴口罩人脸检测和戴口罩识别AndroidDemoAPP_口罩识别论文及源代码-Android文档类资源-CSDN下载

图片测试视频测试

(5) 运行APP闪退:dlopen failed: library "libomp.so" not found

参考解决方法:
解决dlopen failed: library “libomp.so“ not found_PKing666666的博客-CSDN博客_dlopen failed


6.项目源码下载

Android项目源码下载地址:Android实现戴口罩人脸检测和戴口罩识别(附Android源码)

整套Android项目源码内容包含:

  1. 提供Android版本的人脸检测(支持戴口罩人脸检测)
  2. 提供戴口罩识别Android Demo源码
  3. Android Demo在普通手机CPU/GPU上可以实时检测和识别,约30ms左右
  4. Android Demo支持图片,视频,摄像头测试

戴口罩人脸检测和戴口罩识别Android Demo APP体检:https://pan.baidu.com/s/1meGv_J6xZiDvXzvXBzNnHA 提取码: 73e5 或者戴口罩人脸检测和戴口罩识别AndroidDemoAPP_口罩识别论文及源代码-Android文档类资源-CSDN下载

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

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

相关文章

【Javassist】快速入门系列05 当有指定方法调用时替换方法调用的内容

系列文章目录 01 在方法体的开头或结尾插入代码 02 使用Javassist实现方法执行时间统计 03 使用Javassist实现方法异常处理 04 使用Javassist更改整个方法体 05 当有指定方法调用时替换方法调用的内容 文章目录系列文章目录前言引入Javassist jar包当有指定方法调用时替换方法…

1. 位1的个数

文章目录题目解法一&#xff1a;右移统计解题思路代码实现复杂度分析解法二&#xff1a;清除最低位解题思路代码实现复杂度分析解法三&#xff1a;分治解题思路代码实现复杂度分析解法四&#xff1a;JDK Integer.bitCount(int i)代码实现题目 编写一个函数&#xff0c;输入是一…

DPU02:国产USB转UART的桥接控制器兼容替代CP2102

目录DPU02芯片特性USB配置数据和序列号设置DPU02应用领域DPU02是高度集成的USB转UART的桥接控制器芯片&#xff0c;可将RS-232设计更新为USB设计&#xff0c;并简化PCB组件空间。DPU02包括了一个USB 2.0全速功能控制器、USB收发器、振荡器、EEPROM和带有完整调制解调控制信号的…

一文详解深度学习冷板式液冷散热技术规范及要求

深度学习 | 东数西算 | 液冷散热 数据挖掘 | 数据分析 | 高性能计算 随着深度学习、东数西算、医药研发、数据分析、数据挖掘、遥感测绘、高性能计算等技术的快速发展&#xff0c;数据中心的创建与日俱增&#xff0c;传统的风冷散热方式已经不同满足数据中心散热的需求&#x…

Huffman二进制编码以及文本的压缩与解压

目录Huffman树转化成二进制编码文本压缩文本解压Huffman树转化成二进制编码 在上一篇博客的末尾&#xff0c;将Huffman树转化成了01 构成的字符串&#xff0c;显然在实际应用中不是这种操作。我们实际想要的是01构成的一串bits&#xff1b;举个例子&#xff1a;字符"A&quo…

HTTP权威指南------URL与资源

目录 URL标准格式 URL快捷方式 动扩展URL 字符 方案详解 web基础中介绍了URI、URL与URN&#xff1b; URI是一类更通用的资源标识符&#xff0c;URL是它的一个子集&#xff1b; URI是一个通用的概念&#xff0c;它主要由URL与URN组成&#xff1b; URL是通过描述资源的位…

【Kafka】Linux下搭建kafka服务,完整学习案例

【Kafka】Linux下搭建kafka服务&#xff0c;完整学习代码案例&#xff08;一&#xff09;Kafka架构基础【1】图解kafka是什么&#xff1f;&#xff08;1&#xff09;为什么需要消息队列&#xff08;2&#xff09;Topic主题&#xff08;3&#xff09;分区&#xff08;4&#xff…

【UE4 第一人称射击游戏】06-设置动画角色2

步骤&#xff1a; 1.打开“WalkRun_BS”&#xff0c;将最左边中间的点和最右边中间的点的动画改为“walk_backward_inPlace” 2.打开“SWAT_AnimBP”&#xff0c;双击“Walk_Run” 双击“Walk_Run” 将混合空间“WalkRun_BS”拖入 将“Direction”和“Speed”提升为变量&#…

车载以太网 - 初识DoIP - 01

1、DoIP是什么? 从表达形式上 它就是UDS诊断套上车载以太网的马甲,然后实现UDS诊断的所有内容。通过下图DoIP的报文帧格式,从下面往上看,最尾部的数据我们能够很清晰的看到,User Data实际上就是UDS诊断数据,比如:10 03亦或是22 F1 86等信息。然后再往前就是源地址和目标…

Python通过Spleeter实现音唱人声(歌声)伴奏分离

程序示例精选 Python实现音唱人声(歌声)伴奏分离 如需安装运行环境或远程调试&#xff0c;见文章底部微信名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《Python实现音唱人声(歌声)伴奏分离》编写代码&#xff0c;功能包括了人声(歌声)-伴奏分离。运…

Dubbo、Spring Cloud和kubernetes该如何选型?

文章目录背景微服务的公共关注点微服务平台的选型横向比对微服务优劣比对背景 做技术选型。如果缺乏足够的经验&#xff0c;对so a啊&#xff0c;微服架构原理以及整个行业服务化演进的历史缺乏了解。 那么&#xff0c;对这个三个产品该如何选择啊&#xff1f;的确会感到困惑。…

想做副业怎么才能找到适合的项目,六条建议让你找副业不再迷茫

大家好&#xff0c;我是蝶衣王的小编 现在疫情反复&#xff0c;赚钱太难了。许多年轻人想发展副业。而现实情况往往是不知道做什么以及如何做&#xff0c;这是非常令人困惑的。我在这里分享六个要点&#xff0c;让你找到合适的项目。 六个步骤分别是 会观察、观察相关信息和赚…

有趣的网站分享——福音戰士標題生成器

说起文字标题生成器其实也是有一定的历史了。 他们往往源于某种媒介所带来的个性化标题的出现&#xff08;比如动画或电影、广告、产品Logo等&#xff09;。 被互联网群众察觉分享后在模因传播的现象下深入人心。 比如下面这样的&#xff1a; 就出自知名交友网站P…… 额咳。…

快速入门Spring MVC 一篇就够了

前言 我们前面学习了Spring两大核心机制IoC和AOP&#xff0c;接下来我们一起来学习Spring MVC。这篇文章带你快速入门Spring MVC。 Spring MVC概述 Spring MVC是目前主流的实现MVC设计模式的框架&#xff0c;是Spring框架的一个分支产品&#xff0c;以Spring loC容器为基础&…

Fabric.js 铅笔笔刷

本文简介 点赞 关注 收藏 学会了 fabric.js 的铅笔其实是继承基础画笔的一个工具&#xff0c;在基础画笔的基础上多了“拐角平滑度”等配置项。 本文讲解铅笔的基础用法以及常用事件。 常规配置 真实世界的铅笔有不同的型号&#xff0c;颜色的深浅、笔芯的硬度都是不同的…

90后,27岁转行软件测试,从月入3000+到月薪过万,打开了人生新篇章~

承蒙时光不弃&#xff0c;感谢努力的自己。以前总是在某些鸡汤文中看到这句话&#xff0c;当时觉得过于矫情&#xff0c;而如今当我突破重重困难成功转行&#xff0c;收获了更好的人生后&#xff0c;才发自内心的也有了这样的感叹。 几个月的努力和辛劳&#xff0c;一时之间难以…

转互联网好难,如何避免无效转行?

如果你现在是在传统行业工作&#xff0c;想转行互联网&#xff0c;应该怎么做呢&#xff1f; 很多人经常会担心自己的行业、专业、年龄等等会是障碍&#xff0c;或者自己没有经验&#xff0c;去面试的时候公司却都需要相关经验的人&#xff0c;怎么办呢&#xff1f; 这篇文章…

图形驱动软件栈

图形驱动软件栈 HINZER&#xff0c;2022年&#xff0c;我在北京。芯片设计行业&#xff0c;GPU 固件和驱动开发&#xff0c;对嵌入式系统感兴趣。 1 说明背景 1.1 近来想法 做了一段时间的 GPU 固件和驱动开发&#xff0c;加上平时学习的一些零散的知识&#xff0c;最近打算整…

【Web前端HTML5CSS3】06、盒模型

六、盒模型 1、文档流&#xff08;normalflow&#xff09; 网页是一个多层的结构&#xff0c;一层摁着一层 通过 CSS 可以分别为每一层来设置样式&#xff0c;作为用户来讲只能看到最顶上一层 这些层中&#xff0c;最底下的一层称为文档流 文档流是网页的基础我们所创建的元…

牛客java刷题知识点总结(八)

方法调用 类中变量&#xff1a; 除了private权限外&#xff0c;其他权限的变量&#xff08;没有表示默认default&#xff09;&#xff0c;均可以用“对象.变量名”来调用。对于private变量&#xff0c;即使使用static&#xff0c;也不能用“类.变量名”来调用私有变量。只能通过…