本篇博客和大家分享一个基于yolov5的区域入侵检测小项目,在该项目中,我们可以在手机屏幕上面绘制任意形状的闭合区域,然后可以选择需要检测的入侵目标,当目标进入我们绘制的封闭区域内,检测框的颜色将变成红色,并且手机发出警报声。
基于yolov5的区域入侵检测
b站视频地址:https://www.bilibili.com/video/BV1ua411f7iC/?vd_source=65a01bd1c4223f2aede873e40c0cdb3e
app下载地址:https://www.pgyer.com/qTuN
首先需要自定义一个view组件用于绘制监控的区域,其代码如下所示:
package com.myapp.area;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
public class GraffitiView extends View {
private final Context mContext;
private Canvas mCanvas;//
private Bitmap mBitmap;// 用于保存绘制过的路径的 bitmap
private Paint mPaint;// 画笔
private Path mPath;// 触摸时的路径
private int width,height;
public GraffitiView(Context context) {
this(context,null);
}
public GraffitiView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
private void init() {
// 初始化 画笔
mPaint = new Paint();
mPaint.setColor(mContext.getColor(R.color.teal_200));//画笔颜色
mPaint.setAntiAlias(true);// 抗锯齿
mPaint.setDither(true);// 抖动处理
mPaint.setStrokeJoin(Paint.Join.ROUND);//画笔连接处 圆弧
mPaint.setStrokeCap(Paint.Cap.ROUND);//画笔拐弯处风格 圆弧
mPaint.setStyle(Paint.Style.STROKE);//画笔格式
mPaint.setStrokeWidth(10f);//画笔宽度
mPath = new Path();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getMeasuredWidth();
height = getMeasuredHeight();
if(mBitmap == null){
// 初始化 bitmap
mBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_4444);
}
if(mCanvas == null){
mCanvas = new Canvas(mBitmap);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制路径
// 因为每次触摸都会生成一条新的路径,直接绘制会使原路径消失,因此
mCanvas.drawPath(mPath,mPaint);// 先将路径绘制到 bitmap 上,再绘制到当前画布中
canvas.drawBitmap(mBitmap, 0,0,mPaint);// 将bitmap绘制到当前画布中
}
/**
* 清除之前所有路径
*/
public void clearAllPath(){
mBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_4444);
mCanvas = new Canvas(mBitmap);
mPath.reset();
Myview.path=mPath;
//Log.i("aa",mPath+"");
invalidate();
}
/**
* 设置画笔颜色
* @param resource id
*/
public void setPaintColor(int resource){
mPaint.setColor(mContext.getColor(resource));
}
/**
* 设置画笔大小
* @param size size
*/
public void setPaintSize(int size){
mPaint.setStrokeWidth(size);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!MainActivity.draw){
return true;
}
//Log.i("aa","wwww");
int action = event.getAction();
float x = event.getX();
float y = event.getY();
switch (action){
case MotionEvent.ACTION_DOWN:
mPath = new Path();// 每次触摸 生成一条新的路径
mPath.moveTo(x,y);
break;
case MotionEvent.ACTION_MOVE:
Log.i("aa",mPath+"");
mPath.lineTo(x,y);
invalidate();
break;
case MotionEvent.ACTION_UP:
mPath.close();
invalidate();
MainActivity.draw=false;
Myview.path=mPath;
//MainActivity.button_draw.setBackgroundColor(Color.rgb(211,211,211));
MainActivity.button_draw.setText("绘制区域");
//boolean conn=iscontain(mPath,500,500);
break;
}
return true;
}
private boolean iscontain(Path path,int x,int y){
RectF bounds = new RectF();
path.computeBounds(bounds, true);
Region region = new Region();
region.setPath(path, new Region((int)bounds.left, (int)bounds.top,(int)bounds.right, (int)bounds.bottom));
if (region.contains(x,y)){
// Log.i("aa","包含");
return true;
}else {
// Log.i("aa","不包含");
return false;
}
}
}
在本案例中我们使用yolov5作为目标检测网络,无需训练,直接使用yolov5原作者提供的基于coco数据集的预训练模型即可。使用ncnn进行前向推理,其推理代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
// ncnn
#include "layer.h"
#include "net.h"
#include "benchmark.h"
static ncnn::UnlockedPoolAllocator g_blob_pool_allocator;
static ncnn::PoolAllocator g_workspace_pool_allocator;
static ncnn::Net YOLO;
struct Object
{
float x;
float y;
float w;
float h;
int label;
float prob;
};
static float soft_sum(std::vector<float>& v){
float sum=0;
float len=0;
for(float f:v){
sum+=f;
}
for (int i = 0; i < v.size(); i++)
{
float a=v[i]/sum*i;
// v[i]=a;
len+=a;
}
return len;
}
static inline float intersection_area(const Object& a, const Object& b)
{
float zuo_x=std::max(a.x-0.5*a.w,b.x-0.5*b.w);
float zuo_y=std::max(a.y-0.5*a.h,b.y-0.5*b.h);
float you_x=std::min(a.x+0.5*a.w,b.x+0.5*b.w);
float you_y=std::min(a.y+0.5*a.h,b.y+0.5*b.h);
float inter_width=you_x-zuo_x;
float inter_height=you_y-zuo_y;
if(inter_height<=0 || inter_width<=0){
return 0.f;
}
return inter_width * inter_height;
}
static void qsort_descent_inplace(std::vector<Object>& faceobjects, int left, int right)
{
int i = left;
int j = right;
float p = faceobjects[(left + right) / 2].prob;
while (i <= j)
{
while (faceobjects[i].prob > p)
i++;
while (faceobjects[j].prob < p)
j--;
if (i <= j)
{
// swap
std::swap(faceobjects[i], faceobjects[j]);
i++;
j--;
}
}
#pragma omp parallel sections
{
#pragma omp section
{
if (left < j) qsort_descent_inplace(faceobjects, left, j);
}
#pragma omp section
{
if (i < right) qsort_descent_inplace(faceobjects, i, right);
}
}
}
static void qsort_descent_inplace(std::vector<Object>& faceobjects)
{
if (faceobjects.empty())
return;
qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1);
}
static void nms_sorted_bboxes(const std::vector<Object>& faceobjects, std::vector<int>& picked, float nms_threshold)
{
picked.clear();
const int n = faceobjects.size();
std::vector<float> areas(n);
for (int i = 0; i < n; i++)
{
areas[i] = faceobjects[i].w * faceobjects[i].h;
}
for (int i = 0; i < n; i++)
{
const Object& a = faceobjects[i];
int keep = 1;
for (int j = 0; j < (int)picked.size(); j++)
{
const Object& b = faceobjects[picked[j]];
// intersection over union
float inter_area = intersection_area(a, b);
float union_area = areas[i] + areas[picked[j]] - inter_area;
// float IoU = inter_area / union_area
if (inter_area / union_area > nms_threshold)
keep = 0;
}
if (keep)
picked.push_back(i);
}
}
static inline float sigmoid(float x)
{
return static_cast<float>(1.f / (1.f + exp(-x)));
}
static void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector<Object>& objects)
{
const int num_grid = feat_blob.h;
int num_grid_x;
int num_grid_y;
if (in_pad.w > in_pad.h)
{
num_grid_x = in_pad.w / stride;
num_grid_y = num_grid / num_grid_x;
}
else
{
num_grid_y = in_pad.h / stride;
num_grid_x = num_grid / num_grid_y;
}
const int num_class = feat_blob.w - 5;
const int num_anchors = anchors.w / 2;
for (int q = 0; q < num_anchors; q++)
{
const float anchor_w = anchors[q * 2];
const float anchor_h = anchors[q * 2 + 1];
const ncnn::Mat feat = feat_blob.channel(q);
for (int i = 0; i < num_grid_y; i++)
{
for (int j = 0; j < num_grid_x; j++)
{
//获取每一行数据
const float* featptr = feat.row(i * num_grid_x + j);
// find class index with max class score
int class_index = 0;
float class_score = -FLT_MAX;
//求取每一行的最大值和最大值的索引
for (int k = 0; k < num_class; k++)
{
float score = featptr[5 + k];
if (score > class_score)
{
class_index = k;
class_score = score;
}
}
//获取盒子的置信度分数
float box_score = featptr[4];
//获取总的置信度分数
float confidence = sigmoid(box_score) * sigmoid(class_score);
if (confidence >= prob_threshold)
{
// yolov5/models/yolo.py Detect forward
// y = x[i].sigmoid()
// y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i] # xy
// y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
float dx = sigmoid(featptr[0]);
float dy = sigmoid(featptr[1]);
float dw = sigmoid(featptr[2]);
float dh = sigmoid(featptr[3]);
float pb_cx = (dx * 2.f - 0.5f + j) * stride;
float pb_cy = (dy * 2.f - 0.5f + i) * stride;
float pb_w = pow(dw * 2.f, 2) * anchor_w;
float pb_h = pow(dh * 2.f, 2) * anchor_h;
float x0 = pb_cx - pb_w * 0.5f;
float y0 = pb_cy - pb_h * 0.5f;
float x1 = pb_cx + pb_w * 0.5f;
float y1 = pb_cy + pb_h * 0.5f;
Object obj;
obj.x = x0;
obj.y = y0;
obj.w = x1 - x0;
obj.h = y1 - y0;
obj.label = class_index;
obj.prob = confidence;
objects.push_back(obj);
}
}
}
}
}
// FIXME DeleteGlobalRef is missing for objCls
static jclass objCls = NULL;
static jmethodID constructortorId;
static jfieldID xId;
static jfieldID yId;
static jfieldID wId;
static jfieldID hId;
static jfieldID labelId;
static jfieldID probId;
//初始化函数
extern "C" JNIEXPORT jboolean JNICALL
Java_com_myapp_area_Yolov5n_Init(JNIEnv *env, jobject thiz, jobject assetManager) {
// TODO: implement Init()
ncnn::Option opt;
opt.lightmode = true;
opt.num_threads = 4;
opt.blob_allocator = &g_blob_pool_allocator;
opt.workspace_allocator = &g_workspace_pool_allocator;
opt.use_packing_layout = true;
// use vulkan compute
if (ncnn::get_gpu_count() != 0)
opt.use_vulkan_compute = true;
AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
YOLO.opt = opt;
// init param
{
int ret = YOLO.load_param(mgr, "model.param");
if (ret != 0)
{
__android_log_print(ANDROID_LOG_DEBUG, "aa", "load_param failed");
return JNI_FALSE;
}
}
// init bin
{
int ret = YOLO.load_model(mgr, "model.bin");
if (ret != 0)
{
__android_log_print(ANDROID_LOG_DEBUG, "aa", "load_model failed");
return JNI_FALSE;
}
}
// init jni glue
//获取java中的对应实例类
jclass localObjCls = env->FindClass("com/myapp/area/Yolov5n$Obj");
objCls = reinterpret_cast<jclass>(env->NewGlobalRef(localObjCls));
constructortorId = env->GetMethodID(objCls, "" , "(Lcom/myapp/area/Yolov5n;)V");
xId = env->GetFieldID(objCls, "x", "F");
yId = env->GetFieldID(objCls, "y", "F");
wId = env->GetFieldID(objCls, "w", "F");
hId = env->GetFieldID(objCls, "h", "F");
labelId = env->GetFieldID(objCls, "label", "Ljava/lang/String;");
probId = env->GetFieldID(objCls, "prob", "F");
return JNI_TRUE;
}
static int max(int a,int b){
if (a>b){
return a;
} else{return b;}
}
static int min(int a,int b){
if (a>b){
return b;
} else{return a;}
}
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_myapp_area_Yolov5n_Detect(JNIEnv *env, jobject thiz, jobject bitmap, jboolean use_gpu,jfloat prob_threshold ) {
// TODO: implement Detect()
if (use_gpu == JNI_TRUE && ncnn::get_gpu_count() == 0)
{
return NULL;
//return env->NewStringUTF("no vulkan capable gpu");
}
//计数当前时间
double start_time = ncnn::get_current_time();
AndroidBitmapInfo info;
AndroidBitmap_getInfo(env, bitmap, &info);
//原始图像的宽和高
const int width = info.width;
const int height = info.height;
if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
return NULL;
// ncnn from bitmap
const int target_size = 640;
// letterbox pad to multiple of 32
int w = width;
int h = height;
float scale = 1.f;
if (w > h)
{
scale = (float)target_size / w;
w = target_size;
h = h * scale;
}
else
{
scale = (float)target_size / h;
h = target_size;
w = w * scale;
}
ncnn::Mat in = ncnn::Mat::from_android_bitmap_resize(env, bitmap, ncnn::Mat::PIXEL_RGB, w, h);
// pad to target_size rectangle
// yolov5/utils/datasets.py letterbox
int wpad = (w + 31) / 32 * 32 - w;
int hpad = (h + 31) / 32 * 32 - h;
ncnn::Mat in_pad;
ncnn::copy_make_border(in, in_pad, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 114.f);
const float norm_vals[3] = {1 / 255.f, 1 / 255.f, 1 / 255.f};
in_pad.substract_mean_normalize(0, norm_vals);
// in = ncnn::Mat::from_android_bitmap_resize(env, bitmap, ncnn::Mat::PIXEL_RGB, target_size, target_size);
// //const float norm_vals[3] = {1 / 255.f, 1 / 255.f, 1 / 255.f};
// in.substract_mean_normalize(0, norm_vals);
ncnn::Extractor ex = YOLO.create_extractor();
ex.set_vulkan_compute(use_gpu);
ex.input("images", in_pad);
// prob_threshold = 0.25f;
const float nms_threshold = 0.45f;
//符合要求的盒子都放入该容器中
std::vector<Object> objects;
std::vector<Object> proposals;
// stride 8
{
ncnn::Mat out;
ex.extract("output", out);
ncnn::Mat anchors(6);
anchors[0] = 10.f;
anchors[1] = 13.f;
anchors[2] = 16.f;
anchors[3] = 30.f;
anchors[4] = 33.f;
anchors[5] = 23.f;
std::vector<Object> objects8;
generate_proposals(anchors, 8, in_pad, out, prob_threshold, objects8);
proposals.insert(proposals.end(), objects8.begin(), objects8.end());
}
// stride 16
{
ncnn::Mat out;
ex.extract("365", out);
ncnn::Mat anchors(6);
anchors[0] = 30.f;
anchors[1] = 61.f;
anchors[2] = 62.f;
anchors[3] = 45.f;
anchors[4] = 59.f;
anchors[5] = 119.f;
std::vector<Object> objects16;
generate_proposals(anchors, 16, in_pad, out, prob_threshold, objects16);
proposals.insert(proposals.end(), objects16.begin(), objects16.end());
}
// stride 32
{
ncnn::Mat out;
ex.extract("385", out);
ncnn::Mat anchors(6);
anchors[0] = 116.f;
anchors[1] = 90.f;
anchors[2] = 156.f;
anchors[3] = 198.f;
anchors[4] = 373.f;
anchors[5] = 326.f;
std::vector<Object> objects32;
generate_proposals(anchors, 32, in_pad, out, prob_threshold, objects32);
proposals.insert(proposals.end(), objects32.begin(), objects32.end());
}
// sort all proposals by score from highest to lowest
qsort_descent_inplace(proposals);
// apply nms with nms_threshold
std::vector<int> picked;
nms_sorted_bboxes(proposals, picked, nms_threshold);
int count = picked.size();
//objects size 0=>2
objects.resize(count);
for (int i = 0; i < count; i++)
{
objects[i] = proposals[picked[i]];
// adjust offset to original unpadded
float x0 = (objects[i].x - (wpad / 2)) / scale;
float y0 = (objects[i].y - (hpad / 2)) / scale;
float x1 = (objects[i].x + objects[i].w - (wpad / 2)) / scale;
float y1 = (objects[i].y + objects[i].h - (hpad / 2)) / scale;
// x0 = std::max(std::min(x0/640.f, 1.f), 0.f);
// y0 = std::max(std::min(y0/640.f, 1.f), 0.f);
// x1 = std::max(std::min(x1/640.f, 1.f), 0.f);
// y1 = std::max(std::min(y1/640.f, 1.f), 0.f);
// clip
x0 = std::max(std::min(x0, (float)(width - 1)), 0.f);
y0 = std::max(std::min(y0, (float)(height - 1)), 0.f);
x1 = std::max(std::min(x1, (float)(width - 1)), 0.f);
y1 = std::max(std::min(y1, (float)(height - 1)), 0.f);
objects[i].x = x0;
objects[i].y = y0;
objects[i].w = x1 - x0;
objects[i].h = y1 - y0;
}
// objects to Obj[]
static const char* class_names[] = {
"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light",
"fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
"elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
"skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",
"tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
"sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
"potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone",
"microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
"hair drier", "toothbrush"
};
jobjectArray jObjArray = env->NewObjectArray(objects.size(), objCls, NULL);
for (size_t i=0; i<objects.size(); i++)
{
jobject jObj = env->NewObject(objCls, constructortorId, thiz);
env->SetFloatField(jObj, xId, objects[i].x);
env->SetFloatField(jObj, yId, objects[i].y);
env->SetFloatField(jObj, wId, objects[i].w);
env->SetFloatField(jObj, hId, objects[i].h);
env->SetObjectField(jObj, labelId, env->NewStringUTF(class_names[objects[i].label]));
env->SetFloatField(jObj, probId, objects[i].prob);
env->SetObjectArrayElement(jObjArray, i, jObj);
}
return jObjArray;
}
如下图所示,左侧人物检测框的中心位于封闭区域外部,所以判定其不在封闭区域内,右边人物的检测框中心位于封闭区域内部,则认为其在封闭区域内部,检测框颜色变成红色。
上文中的代码仅仅只是一部分较为重要的代码,全部代码可参考gitee。gitee地址:https://gitee.com/mqwdasddqw/Area。
如果该博客对您有所帮助,请点个关注吧,感谢!!!