赞
踩
上篇位置里提供了实现图片人像换背景相关的资源,这篇我们来看看代码相关的内容。
这里主要使用的是字节跳动开源的rvm,结合腾讯开源的ncnn和opencv来实现,在上篇展示的代码结构图可以看到rvm相关的几个文件,使用jni在android里加载assets里面的rvm文件,使用这些文件结合ncnn来实现人像相关的识别与抠像,人像抠图结果是有纯色背景的,最关键的还是对结果图进行透明化的设置,实现人像抠图透明化后使用opencv来实现抠图人像换背景,这是大体的一个思路。
1、nanodet.h
这个文件里主要定义相关方法,可以看到有加载rvm、制作人像抠图换背景、开放到android端的native等方法。
- // Tencent is pleased to support the open source community by making ncnn available.
- //
- // Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
- //
- // Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
- // in compliance with the License. You may obtain a copy of the License at
- //
- // https://opensource.org/licenses/BSD-3-Clause
- //
- // Unless required by applicable law or agreed to in writing, software distributed
- // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
- // CONDITIONS OF ANY KIND, either express or implied. See the License for the
- // specific language governing permissions and limitations under the License.
-
- #ifndef NANODET_H
- #define NANODET_H
-
- #include <jni.h>
-
- #include <opencv2/core/core.hpp>
-
- #include <net.h>
-
- struct Object
- {
- cv::Rect_<float> rect;
- cv::Mat mask;
- int label;
- float prob;
- };
-
- class NanoDet
- {
- public:
- NanoDet();
-
- int load(const char* modeltype, int target_size, const float* mean_vals, const float* norm_vals, bool use_gpu = false);
-
- int load(AAssetManager* mgr, const char* modeltype, int target_size, const float* mean_vals, const float* norm_vals, bool use_gpu = false);
-
- int detect(const cv::Mat& rgb, std::vector<Object>& objects, float prob_threshold = 0.4f, float nms_threshold = 0.5f);
-
- int draw(cv::Mat& rgb,cv::Mat& e, const std::vector<Object>& objects);
-
- int detect_rvm(const cv::Mat& bgr, cv::Mat& pha, cv::Mat& fgr);
-
- void draw_objects(const cv::Mat& bgr,cv::Mat& e, const cv::Mat& fgr, const cv::Mat& pha);
-
- void imageOverlayD(const cv::Mat&people, cv::Mat&background, int x, int y);
-
- int addAlpha(cv::Mat& src, cv::Mat& dst, cv::Mat& alpha);
-
- cv::Mat createAlpha(cv::Mat& src);
-
- int imageHumanMatting(const char* imagepath);
-
- int imageQualityChange(cv::Mat& src,float strength);
-
- int imageQualityChange(JNIEnv *,jclass,jstring imagepath,float strength);
-
- private:
-
- void matting(cv::Mat &rgb, cv::Mat &mask, cv::Mat &fgr);
- ncnn::Net faceseg;
-
- ncnn::Mat r1i, r2i, r3i, r4i;
- ncnn::Mat r4o, r3o, r2o, r1o;
- int is_first;
- int target_size;
- float mean_vals[3];
- float norm_vals[3];
-
-
- ncnn::UnlockedPoolAllocator blob_pool_allocator;
- ncnn::PoolAllocator workspace_pool_allocator;
- };
-
- #endif // NANODET_H
2、实现加载android assets中rvm。
- int
- NanoDet::load(AAssetManager *mgr, const char *modeltype, int _target_size, const float *_mean_vals,
- const float *_norm_vals, bool use_gpu) {
- faceseg.clear();
- blob_pool_allocator.clear();
- workspace_pool_allocator.clear();
-
- ncnn::set_cpu_powersave(2);
- ncnn::set_omp_num_threads(ncnn::get_big_cpu_count());
-
- faceseg.opt = ncnn::Option();
-
- #if NCNN_VULKAN
- faceseg.opt.use_vulkan_compute = use_gpu;
- #endif
-
- faceseg.opt.num_threads = ncnn::get_big_cpu_count();
- faceseg.opt.blob_allocator = &blob_pool_allocator;
- faceseg.opt.workspace_allocator = &workspace_pool_allocator;
- char parampath[256];
- char modelpath[256];
- sprintf(parampath, "%s.param", modeltype);
- sprintf(modelpath, "%s.bin", modeltype);
-
- // faceseg.load_param(mgr, parampath);
- // faceseg.load_model(mgr, modelpath);
-
-
- faceseg.load_param(mgr, parampath);
- faceseg.load_model(mgr, modelpath);
-
- target_size = _target_size;
- mean_vals[0] = _mean_vals[0];
- mean_vals[1] = _mean_vals[1];
- mean_vals[2] = _mean_vals[2];
- norm_vals[0] = _norm_vals[0];
- norm_vals[1] = _norm_vals[1];
- norm_vals[2] = _norm_vals[2];
-
- if (target_size == 512) {
- r1i = ncnn::Mat(128, 128, 16);
- r2i = ncnn::Mat(64, 64, 20);
- r3i = ncnn::Mat(32, 32, 40);
- r4i = ncnn::Mat(16, 16, 64);
- } else {
- r1i = ncnn::Mat(160, 120, 16);
- r2i = ncnn::Mat(80, 60, 20);
- r3i = ncnn::Mat(40, 30, 40);
- r4i = ncnn::Mat(20, 15, 64);
- }
- r1i.fill(0.0f);
- r2i.fill(0.0f);
- r3i.fill(0.0f);
- r4i.fill(0.0f);
-
- return 0;
- }
3、检测rvm。
- int NanoDet::detect_rvm(const cv::Mat &bgr, cv::Mat &pha, cv::Mat &fgr) {
- const float downsample_ratio = 0.5f;
- const int target_width = 512;
- const int target_height = 512;
-
- faceseg.opt.use_vulkan_compute = false;
- //original pretrained model from https://github.com/PeterL1n/RobustVideoMatting
- //ncnn model https://pan.baidu.com/s/11iEY2RGfzWFtce8ue7T3JQ password: d9t6
- // net.load_param("/data/data/com.tencent.ncnnbodyseg/files/rvm_512.param");
- // net.load_model("/data/data/com.tencent.ncnnbodyseg/files/rvm_512.bin");
-
- //if you use another input size,pleaze change input shape
- ncnn::Mat r1i = ncnn::Mat(128, 128, 16);
- ncnn::Mat r2i = ncnn::Mat(64, 64, 20);
- ncnn::Mat r3i = ncnn::Mat(32, 32, 40);
- ncnn::Mat r4i = ncnn::Mat(16, 16, 64);
- r1i.fill(0.0f);
- r2i.fill(0.0f);
- r3i.fill(0.0f);
- r4i.fill(0.0f);
-
- ncnn::Extractor ex = faceseg.create_extractor();
- const float mean_vals1[3] = {123.675f, 116.28f, 103.53f};
- const float norm_vals1[3] = {0.01712475f, 0.0175f, 0.01742919f};
- const float mean_vals2[3] = {0, 0, 0};
- const float norm_vals2[3] = {1 / 255.0, 1 / 255.0, 1 / 255.0};
- ncnn::Mat ncnn_in2 = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR2RGB, bgr.cols,
- bgr.rows, target_width, target_height);
- ncnn::Mat ncnn_in1 = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR2RGB, bgr.cols,
- bgr.rows, target_width * downsample_ratio,
- target_height * downsample_ratio);
-
- ncnn_in1.substract_mean_normalize(mean_vals1, norm_vals1);
- ncnn_in2.substract_mean_normalize(mean_vals2, norm_vals2);
-
- ex.input("src1", ncnn_in1);
- ex.input("src2", ncnn_in2);
- ex.input("r1i", r1i);
- ex.input("r2i", r2i);
- ex.input("r3i", r3i);
- ex.input("r4i", r4i);
-
- //if use video matting,these output will be input of next infer
- ex.extract("r4o", r4i);
- ex.extract("r3o", r3i);
- ex.extract("r2o", r2i);
- ex.extract("r1o", r1i);
-
- ncnn::Mat pha_;
- ex.extract("pha", pha_);
- ncnn::Mat fgr_;
- ex.extract("fgr", fgr_);
-
- cv::Mat cv_pha = cv::Mat(pha_.h, pha_.w, CV_32FC1, (float *) pha_.data);
- cv::Mat cv_fgr = cv::Mat(fgr_.h, fgr_.w, CV_32FC3);
- float *fgr_data = (float *) fgr_.data;
- for (int i = 0; i < fgr_.h; i++) {
- for (int j = 0; j < fgr_.w; j++) {
- cv_fgr.at<cv::Vec3f>(i, j)[2] = fgr_data[0 * fgr_.h * fgr_.w + i * fgr_.w + j];
- cv_fgr.at<cv::Vec3f>(i, j)[1] = fgr_data[1 * fgr_.h * fgr_.w + i * fgr_.w + j];
- cv_fgr.at<cv::Vec3f>(i, j)[0] = fgr_data[2 * fgr_.h * fgr_.w + i * fgr_.w + j];
- }
- }
-
- cv_pha.copyTo(pha);
- cv_fgr.copyTo(fgr);
-
- return 0;
- }
4、人像抠图并实现结果透明化和画质增强。
-
- void NanoDet::draw_objects(const cv::Mat &bgr, cv::Mat &e, const cv::Mat &fgr, const cv::Mat &pha) {
- cv::Mat fgr8U;
- fgr.convertTo(fgr8U, CV_8UC3, 255.0, 0);
- cv::Mat pha8U;
- pha.convertTo(pha8U, CV_8UC1, 255.0, 0);
-
- if (bgr.size().empty()) {
-
- return;
- }
-
- cv::Mat comp;
- cv::resize(bgr, comp, pha.size(), 0, 0, 1);
- for (int i = 0; i < pha8U.rows; i++) {
- for (int j = 0; j < pha8U.cols; j++) {
- uchar data = pha8U.at<uchar>(i, j);
- float alpha = (float) data / 255;
- comp.at<cv::Vec3b>(i, j)[0] =
- fgr8U.at<cv::Vec3b>(i, j)[0] * alpha + (1 - alpha) * 0;//155
- comp.at<cv::Vec3b>(i, j)[1] =
- fgr8U.at<cv::Vec3b>(i, j)[1] * alpha + (1 - alpha) * 0;//255
- comp.at<cv::Vec3b>(i, j)[2] =
- fgr8U.at<cv::Vec3b>(i, j)[2] * alpha + (1 - alpha) * 0;//120
- }
- }
-
- // cv::imshow("pha", pha8U);
- //cv::imshow("fgr", fgr8U);
- //cv::Mat img1_t1(comp, cv::Rect(0, 0, comp.cols, comp.rows));
- // toPng(comp, img1_t1, 0);
- // cv::imshow("comp", comp);
-
-
-
- cv::Mat img_alpha_0;
-
-
- //
- cv::resize(comp, comp, cv::Size(bgr.cols, bgr.rows), 0, 0, cv::INTER_LINEAR);
- toPng(comp, img_alpha_0, 0);
-
-
-
- cv::Mat alpha = cv::Mat::zeros(img_alpha_0.rows, img_alpha_0.cols, CV_8UC1);
- cv::Mat gray = cv::Mat::zeros(img_alpha_0.rows, img_alpha_0.cols, CV_8UC1);
-
- cv::cvtColor(img_alpha_0, gray, cv::COLOR_RGB2GRAY);
-
- for (int i = 0; i < img_alpha_0.rows; i++) {
- for (int j = 0; j < img_alpha_0.cols; j++) {
- alpha.at<uchar>(i, j) = gray.at<uchar>(i, j);
- }
- }
-
- cv::Mat dst = cv::Mat(img_alpha_0.rows, img_alpha_0.cols, CV_8UC4);
-
- std::vector<cv::Mat> srcChannels;
- std::vector<cv::Mat> dstChannels;
- //分离通道
- cv::split(img_alpha_0, srcChannels);
-
- dstChannels.push_back(srcChannels[0]);
- dstChannels.push_back(srcChannels[1]);
- dstChannels.push_back(srcChannels[2]);
- //添加透明度通道
- dstChannels.push_back(alpha);
- //合并通道
- cv::merge(dstChannels, dst);
-
-
-
- imwrite("/storage/emulated/0/DCIM/Camera/aaaac.png", dst);
-
- cv::Mat ed = cv::imread("/storage/emulated/0/DCIM/Camera/aaaac.png",-1);
-
- cv::Mat ebg = cv::imread("/storage/emulated/0/DCIM/Camera/hbg.jpg");
-
-
- imageOverlayD(ed, ebg, 550, 667);
-
-
- imwrite("/storage/emulated/0/DCIM/Camera/aaaacdd.jpg", ebg);
-
-
- Mat dstT;
-
- cv::GaussianBlur(ebg, dstT, cv::Size(0, 0), 9);
- cv::addWeighted(ebg, 1.5, dstT, -0.335, 0, dstT);
-
- imwrite("/storage/emulated/0/DCIM/Camera/aaaactt.jpg", dstT);
-
- cv::waitKey(0);
- }
-
- // 图像叠加函数的实现
- void NanoDet::imageOverlayD(const Mat &people, Mat &background, int x, int y)
- {
- int channelNum = 3; // rgb通道数为3
- int alpha = 1; // alpha值表示图像透明度,0代表完全透明,1代表完全不透明
-
- for (int i = 0; i < people.rows; i++)
- {
- for(int j = 0; j < people.cols * 3; j += 3)
- {
- alpha = people.ptr<uchar>(i)[j / 3*4 + 3];
-
- if(alpha != 0)
- {
- for (int k = 0; k < 3; k++)
- {
- // jpg不存在alpha通道,所以给读取出来的数组增加一个alpha的维度
- if( (i+y < background.rows) && (i+y>=0) &&
- ((j+x*3) / 3*3 + k < background.cols*3) && ((j+x*3) / 3*3 + k >= 0) &&
- (i/channelNum*4 + k < background.cols*4) && (j/channelNum*4 + k >=0) )
- {
- background.ptr<uchar>(i+y)[(j+x*channelNum) / channelNum*channelNum + k] = people.ptr<uchar>(i)[(j) / channelNum*4 + k];
- }
- }
- }
- }
- }
- }
5、jni native实现-加载android assets rvm文件
- JNIEXPORT jboolean JNICALL Java_com_tencent_ncnnbodyseg_NcnnBodyseg_loadModel(JNIEnv* env, jobject thiz, jobject assetManager, jint modelid, jint cpugpu)
- {
- if (modelid < 0 || modelid > 6 || cpugpu < 0 || cpugpu > 1)
- {
- return JNI_FALSE;
- }
-
- AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
-
- __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "loadModel %p", mgr);
-
- const char* modeltypes[] =
- {
- "rvm-512",
- "rvm-640",
- };
-
- const int target_sizes[] =
- {
- 512,
- 640,
- };
-
- const float mean_vals[][3] =
- {
- {123.675f, 116.28f, 103.53f},
- {123.675f, 116.28f, 103.53f},
- };
-
- const float norm_vals[][3] =
- {
- {0.01712475f, 0.0175f, 0.01742919f},
- {0.01712475f, 0.0175f, 0.01742919f},
- };
-
- const char* modeltype = modeltypes[(int)modelid];
- int target_size = target_sizes[(int)modelid];
- bool use_gpu = (int)cpugpu == 1;
-
- // reload
- {
- ncnn::MutexLockGuard g(lock);
-
- if (use_gpu && ncnn::get_gpu_count() == 0)
- {
- // no gpu
- delete g_nanodet;
- g_nanodet = 0;
- }
- else
- {
- if (!g_nanodet)
- g_nanodet = new NanoDet;
- g_nanodet->load(mgr, modeltype, target_size, mean_vals[(int)modelid], norm_vals[(int)modelid], use_gpu);
- }
- }
-
- return JNI_TRUE;
- }
6、jni native实现-执行人像抠图换背景
-
- JNIEXPORT jint JNICALL Java_com_yiachang_keepmemo_jni_KeepOpencvNcnn_imageQualityChange(JNIEnv *env, jclass clazz, jstring imagepath,jfloat strength)
- {
-
-
- std::vector<Object> objects;
-
- std::string path = (char *) env->GetStringUTFChars(imagepath, 0);
-
- cv::Mat m = cv::imread(path);
-
- int result = g_nanodet->imageQualityChange(m, strength);
-
- jmethodID methodID = env->GetStaticMethodID(clazz, "onExecuted", "(I)V");
-
- //调用该java方法
- env->CallStaticVoidMethod(clazz, methodID,result);
-
- return 0;
-
- }
7、android 端native相关接口
- public class KeepOpencvNcnn
- {
- public native boolean loadModel(AssetManager mgr, int modelid, int cpugpu);
- public native int imageHumanMatting(String imagepath);
- public static native int imageQualityChange(String imagepath,float strength);
-
-
- static {
- System.loadLibrary("keepOpencvNcnn");
- }
-
-
- public static void exec(String imagepath,float strength, KoNListener listener)
- {
- mKoNListener = listener;
-
- imageQualityChange(imagepath, strength);
-
- }
-
-
- public static void onExecuted(int n) {
-
- if(n == -1){
-
- if(mKoNListener != null){
- mKoNListener.onFail();
- }
-
- return;
- }
-
- if(mKoNListener != null){
- mKoNListener.onFinish();
- }
-
- }
-
- static KoNListener mKoNListener;
- /**
- * 回调监听
- */
- public interface KoNListener {
-
- /**
- *
- */
- void onFinish();
-
- /**
- *
- */
- void onFail();
-
- }
-
-
- }
以上是所有相关的代码内容了,特别要注意里面相关的图片文件路径,如果使用相关代码的话需要准备相关路径的图片,或者替换里面的图片路径。
重点摘要:
纯背景图片透明化:网上很多事针对PNG图片进行的纯背景图片透明化,以下代码可以实现jpg、png纯背景图片的透明化。
- cv::Mat dst = cv::Mat(img_alpha_0.rows, img_alpha_0.cols, CV_8UC4);
-
- std::vector<cv::Mat> srcChannels;
- std::vector<cv::Mat> dstChannels;
- //分离通道
- cv::split(img_alpha_0, srcChannels);
-
- dstChannels.push_back(srcChannels[0]);
- dstChannels.push_back(srcChannels[1]);
- dstChannels.push_back(srcChannels[2]);
- //添加透明度通道
- dstChannels.push_back(alpha);
- //合并通道
- cv::merge(dstChannels, dst);
图片画质增强:网上也有很多关于对比度、亮度、饱和度等图片画质增强的文章,这里是使用高斯和权重来实现图片画质增强的,不过以下方法可以实现图片画质调整,区间范围是[-1,1],在开放的进度范围是[0,1],也就是说android端可以调整0~1范围的进度(SeekBar),来实现图片画质范围[-1,1]的调整,当然朋友们可以使用以下代码进行调整,实现自己合适的画质区间。
-
- int NanoDet::imageQualityChange(cv::Mat& src,float strength) {
-
-
- Mat dstT;
-
- if (src.size().empty()) {
-
- return -1;
- }
-
- cv::GaussianBlur(src, dstT, cv::Size(0, 0), 9);
- cv::addWeighted(src, 1.5, dstT, -1+strength , 0, dstT);//0~1[-1~1]
-
- imwrite("/storage/emulated/0/DCIM/Camera/aaaactt.jpg", dstT);
-
- cv::waitKey(0);
-
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。