当前位置:   article > 正文

Android-Opencv-Ncnn实现图片人像换背景-代码实施

Android-Opencv-Ncnn实现图片人像换背景-代码实施

上篇位置里提供了实现图片人像换背景相关的资源,这篇我们来看看代码相关的内容。

这里主要使用的是字节跳动开源的rvm,结合腾讯开源的ncnn和opencv来实现,在上篇展示的代码结构图可以看到rvm相关的几个文件,使用jni在android里加载assets里面的rvm文件,使用这些文件结合ncnn来实现人像相关的识别与抠像,人像抠图结果是有纯色背景的,最关键的还是对结果图进行透明化的设置,实现人像抠图透明化后使用opencv来实现抠图人像换背景,这是大体的一个思路。

1、nanodet.h

 这个文件里主要定义相关方法,可以看到有加载rvm、制作人像抠图换背景、开放到android端的native等方法。

  1. // Tencent is pleased to support the open source community by making ncnn available.
  2. //
  3. // Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
  4. //
  5. // Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
  6. // in compliance with the License. You may obtain a copy of the License at
  7. //
  8. // https://opensource.org/licenses/BSD-3-Clause
  9. //
  10. // Unless required by applicable law or agreed to in writing, software distributed
  11. // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  12. // CONDITIONS OF ANY KIND, either express or implied. See the License for the
  13. // specific language governing permissions and limitations under the License.
  14. #ifndef NANODET_H
  15. #define NANODET_H
  16. #include <jni.h>
  17. #include <opencv2/core/core.hpp>
  18. #include <net.h>
  19. struct Object
  20. {
  21. cv::Rect_<float> rect;
  22. cv::Mat mask;
  23. int label;
  24. float prob;
  25. };
  26. class NanoDet
  27. {
  28. public:
  29. NanoDet();
  30. int load(const char* modeltype, int target_size, const float* mean_vals, const float* norm_vals, bool use_gpu = false);
  31. int load(AAssetManager* mgr, const char* modeltype, int target_size, const float* mean_vals, const float* norm_vals, bool use_gpu = false);
  32. int detect(const cv::Mat& rgb, std::vector<Object>& objects, float prob_threshold = 0.4f, float nms_threshold = 0.5f);
  33. int draw(cv::Mat& rgb,cv::Mat& e, const std::vector<Object>& objects);
  34. int detect_rvm(const cv::Mat& bgr, cv::Mat& pha, cv::Mat& fgr);
  35. void draw_objects(const cv::Mat& bgr,cv::Mat& e, const cv::Mat& fgr, const cv::Mat& pha);
  36. void imageOverlayD(const cv::Mat&people, cv::Mat&background, int x, int y);
  37. int addAlpha(cv::Mat& src, cv::Mat& dst, cv::Mat& alpha);
  38. cv::Mat createAlpha(cv::Mat& src);
  39. int imageHumanMatting(const char* imagepath);
  40. int imageQualityChange(cv::Mat& src,float strength);
  41. int imageQualityChange(JNIEnv *,jclass,jstring imagepath,float strength);
  42. private:
  43. void matting(cv::Mat &rgb, cv::Mat &mask, cv::Mat &fgr);
  44. ncnn::Net faceseg;
  45. ncnn::Mat r1i, r2i, r3i, r4i;
  46. ncnn::Mat r4o, r3o, r2o, r1o;
  47. int is_first;
  48. int target_size;
  49. float mean_vals[3];
  50. float norm_vals[3];
  51. ncnn::UnlockedPoolAllocator blob_pool_allocator;
  52. ncnn::PoolAllocator workspace_pool_allocator;
  53. };
  54. #endif // NANODET_H

2、实现加载android assets中rvm。

  1. int
  2. NanoDet::load(AAssetManager *mgr, const char *modeltype, int _target_size, const float *_mean_vals,
  3. const float *_norm_vals, bool use_gpu) {
  4. faceseg.clear();
  5. blob_pool_allocator.clear();
  6. workspace_pool_allocator.clear();
  7. ncnn::set_cpu_powersave(2);
  8. ncnn::set_omp_num_threads(ncnn::get_big_cpu_count());
  9. faceseg.opt = ncnn::Option();
  10. #if NCNN_VULKAN
  11. faceseg.opt.use_vulkan_compute = use_gpu;
  12. #endif
  13. faceseg.opt.num_threads = ncnn::get_big_cpu_count();
  14. faceseg.opt.blob_allocator = &blob_pool_allocator;
  15. faceseg.opt.workspace_allocator = &workspace_pool_allocator;
  16. char parampath[256];
  17. char modelpath[256];
  18. sprintf(parampath, "%s.param", modeltype);
  19. sprintf(modelpath, "%s.bin", modeltype);
  20. // faceseg.load_param(mgr, parampath);
  21. // faceseg.load_model(mgr, modelpath);
  22. faceseg.load_param(mgr, parampath);
  23. faceseg.load_model(mgr, modelpath);
  24. target_size = _target_size;
  25. mean_vals[0] = _mean_vals[0];
  26. mean_vals[1] = _mean_vals[1];
  27. mean_vals[2] = _mean_vals[2];
  28. norm_vals[0] = _norm_vals[0];
  29. norm_vals[1] = _norm_vals[1];
  30. norm_vals[2] = _norm_vals[2];
  31. if (target_size == 512) {
  32. r1i = ncnn::Mat(128, 128, 16);
  33. r2i = ncnn::Mat(64, 64, 20);
  34. r3i = ncnn::Mat(32, 32, 40);
  35. r4i = ncnn::Mat(16, 16, 64);
  36. } else {
  37. r1i = ncnn::Mat(160, 120, 16);
  38. r2i = ncnn::Mat(80, 60, 20);
  39. r3i = ncnn::Mat(40, 30, 40);
  40. r4i = ncnn::Mat(20, 15, 64);
  41. }
  42. r1i.fill(0.0f);
  43. r2i.fill(0.0f);
  44. r3i.fill(0.0f);
  45. r4i.fill(0.0f);
  46. return 0;
  47. }

3、检测rvm。

  1. int NanoDet::detect_rvm(const cv::Mat &bgr, cv::Mat &pha, cv::Mat &fgr) {
  2. const float downsample_ratio = 0.5f;
  3. const int target_width = 512;
  4. const int target_height = 512;
  5. faceseg.opt.use_vulkan_compute = false;
  6. //original pretrained model from https://github.com/PeterL1n/RobustVideoMatting
  7. //ncnn model https://pan.baidu.com/s/11iEY2RGfzWFtce8ue7T3JQ password: d9t6
  8. // net.load_param("/data/data/com.tencent.ncnnbodyseg/files/rvm_512.param");
  9. // net.load_model("/data/data/com.tencent.ncnnbodyseg/files/rvm_512.bin");
  10. //if you use another input size,pleaze change input shape
  11. ncnn::Mat r1i = ncnn::Mat(128, 128, 16);
  12. ncnn::Mat r2i = ncnn::Mat(64, 64, 20);
  13. ncnn::Mat r3i = ncnn::Mat(32, 32, 40);
  14. ncnn::Mat r4i = ncnn::Mat(16, 16, 64);
  15. r1i.fill(0.0f);
  16. r2i.fill(0.0f);
  17. r3i.fill(0.0f);
  18. r4i.fill(0.0f);
  19. ncnn::Extractor ex = faceseg.create_extractor();
  20. const float mean_vals1[3] = {123.675f, 116.28f, 103.53f};
  21. const float norm_vals1[3] = {0.01712475f, 0.0175f, 0.01742919f};
  22. const float mean_vals2[3] = {0, 0, 0};
  23. const float norm_vals2[3] = {1 / 255.0, 1 / 255.0, 1 / 255.0};
  24. ncnn::Mat ncnn_in2 = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR2RGB, bgr.cols,
  25. bgr.rows, target_width, target_height);
  26. ncnn::Mat ncnn_in1 = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR2RGB, bgr.cols,
  27. bgr.rows, target_width * downsample_ratio,
  28. target_height * downsample_ratio);
  29. ncnn_in1.substract_mean_normalize(mean_vals1, norm_vals1);
  30. ncnn_in2.substract_mean_normalize(mean_vals2, norm_vals2);
  31. ex.input("src1", ncnn_in1);
  32. ex.input("src2", ncnn_in2);
  33. ex.input("r1i", r1i);
  34. ex.input("r2i", r2i);
  35. ex.input("r3i", r3i);
  36. ex.input("r4i", r4i);
  37. //if use video matting,these output will be input of next infer
  38. ex.extract("r4o", r4i);
  39. ex.extract("r3o", r3i);
  40. ex.extract("r2o", r2i);
  41. ex.extract("r1o", r1i);
  42. ncnn::Mat pha_;
  43. ex.extract("pha", pha_);
  44. ncnn::Mat fgr_;
  45. ex.extract("fgr", fgr_);
  46. cv::Mat cv_pha = cv::Mat(pha_.h, pha_.w, CV_32FC1, (float *) pha_.data);
  47. cv::Mat cv_fgr = cv::Mat(fgr_.h, fgr_.w, CV_32FC3);
  48. float *fgr_data = (float *) fgr_.data;
  49. for (int i = 0; i < fgr_.h; i++) {
  50. for (int j = 0; j < fgr_.w; j++) {
  51. cv_fgr.at<cv::Vec3f>(i, j)[2] = fgr_data[0 * fgr_.h * fgr_.w + i * fgr_.w + j];
  52. cv_fgr.at<cv::Vec3f>(i, j)[1] = fgr_data[1 * fgr_.h * fgr_.w + i * fgr_.w + j];
  53. cv_fgr.at<cv::Vec3f>(i, j)[0] = fgr_data[2 * fgr_.h * fgr_.w + i * fgr_.w + j];
  54. }
  55. }
  56. cv_pha.copyTo(pha);
  57. cv_fgr.copyTo(fgr);
  58. return 0;
  59. }

4、人像抠图并实现结果透明化和画质增强。

  1. void NanoDet::draw_objects(const cv::Mat &bgr, cv::Mat &e, const cv::Mat &fgr, const cv::Mat &pha) {
  2. cv::Mat fgr8U;
  3. fgr.convertTo(fgr8U, CV_8UC3, 255.0, 0);
  4. cv::Mat pha8U;
  5. pha.convertTo(pha8U, CV_8UC1, 255.0, 0);
  6. if (bgr.size().empty()) {
  7. return;
  8. }
  9. cv::Mat comp;
  10. cv::resize(bgr, comp, pha.size(), 0, 0, 1);
  11. for (int i = 0; i < pha8U.rows; i++) {
  12. for (int j = 0; j < pha8U.cols; j++) {
  13. uchar data = pha8U.at<uchar>(i, j);
  14. float alpha = (float) data / 255;
  15. comp.at<cv::Vec3b>(i, j)[0] =
  16. fgr8U.at<cv::Vec3b>(i, j)[0] * alpha + (1 - alpha) * 0;//155
  17. comp.at<cv::Vec3b>(i, j)[1] =
  18. fgr8U.at<cv::Vec3b>(i, j)[1] * alpha + (1 - alpha) * 0;//255
  19. comp.at<cv::Vec3b>(i, j)[2] =
  20. fgr8U.at<cv::Vec3b>(i, j)[2] * alpha + (1 - alpha) * 0;//120
  21. }
  22. }
  23. // cv::imshow("pha", pha8U);
  24. //cv::imshow("fgr", fgr8U);
  25. //cv::Mat img1_t1(comp, cv::Rect(0, 0, comp.cols, comp.rows));
  26. // toPng(comp, img1_t1, 0);
  27. // cv::imshow("comp", comp);
  28. cv::Mat img_alpha_0;
  29. //
  30. cv::resize(comp, comp, cv::Size(bgr.cols, bgr.rows), 0, 0, cv::INTER_LINEAR);
  31. toPng(comp, img_alpha_0, 0);
  32. cv::Mat alpha = cv::Mat::zeros(img_alpha_0.rows, img_alpha_0.cols, CV_8UC1);
  33. cv::Mat gray = cv::Mat::zeros(img_alpha_0.rows, img_alpha_0.cols, CV_8UC1);
  34. cv::cvtColor(img_alpha_0, gray, cv::COLOR_RGB2GRAY);
  35. for (int i = 0; i < img_alpha_0.rows; i++) {
  36. for (int j = 0; j < img_alpha_0.cols; j++) {
  37. alpha.at<uchar>(i, j) = gray.at<uchar>(i, j);
  38. }
  39. }
  40. cv::Mat dst = cv::Mat(img_alpha_0.rows, img_alpha_0.cols, CV_8UC4);
  41. std::vector<cv::Mat> srcChannels;
  42. std::vector<cv::Mat> dstChannels;
  43. //分离通道
  44. cv::split(img_alpha_0, srcChannels);
  45. dstChannels.push_back(srcChannels[0]);
  46. dstChannels.push_back(srcChannels[1]);
  47. dstChannels.push_back(srcChannels[2]);
  48. //添加透明度通道
  49. dstChannels.push_back(alpha);
  50. //合并通道
  51. cv::merge(dstChannels, dst);
  52. imwrite("/storage/emulated/0/DCIM/Camera/aaaac.png", dst);
  53. cv::Mat ed = cv::imread("/storage/emulated/0/DCIM/Camera/aaaac.png",-1);
  54. cv::Mat ebg = cv::imread("/storage/emulated/0/DCIM/Camera/hbg.jpg");
  55. imageOverlayD(ed, ebg, 550, 667);
  56. imwrite("/storage/emulated/0/DCIM/Camera/aaaacdd.jpg", ebg);
  57. Mat dstT;
  58. cv::GaussianBlur(ebg, dstT, cv::Size(0, 0), 9);
  59. cv::addWeighted(ebg, 1.5, dstT, -0.335, 0, dstT);
  60. imwrite("/storage/emulated/0/DCIM/Camera/aaaactt.jpg", dstT);
  61. cv::waitKey(0);
  62. }
  63. // 图像叠加函数的实现
  64. void NanoDet::imageOverlayD(const Mat &people, Mat &background, int x, int y)
  65. {
  66. int channelNum = 3; // rgb通道数为3
  67. int alpha = 1; // alpha值表示图像透明度,0代表完全透明,1代表完全不透明
  68. for (int i = 0; i < people.rows; i++)
  69. {
  70. for(int j = 0; j < people.cols * 3; j += 3)
  71. {
  72. alpha = people.ptr<uchar>(i)[j / 3*4 + 3];
  73. if(alpha != 0)
  74. {
  75. for (int k = 0; k < 3; k++)
  76. {
  77. // jpg不存在alpha通道,所以给读取出来的数组增加一个alpha的维度
  78. if( (i+y < background.rows) && (i+y>=0) &&
  79. ((j+x*3) / 3*3 + k < background.cols*3) && ((j+x*3) / 3*3 + k >= 0) &&
  80. (i/channelNum*4 + k < background.cols*4) && (j/channelNum*4 + k >=0) )
  81. {
  82. background.ptr<uchar>(i+y)[(j+x*channelNum) / channelNum*channelNum + k] = people.ptr<uchar>(i)[(j) / channelNum*4 + k];
  83. }
  84. }
  85. }
  86. }
  87. }
  88. }

5、jni native实现-加载android assets rvm文件

  1. JNIEXPORT jboolean JNICALL Java_com_tencent_ncnnbodyseg_NcnnBodyseg_loadModel(JNIEnv* env, jobject thiz, jobject assetManager, jint modelid, jint cpugpu)
  2. {
  3. if (modelid < 0 || modelid > 6 || cpugpu < 0 || cpugpu > 1)
  4. {
  5. return JNI_FALSE;
  6. }
  7. AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
  8. __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "loadModel %p", mgr);
  9. const char* modeltypes[] =
  10. {
  11. "rvm-512",
  12. "rvm-640",
  13. };
  14. const int target_sizes[] =
  15. {
  16. 512,
  17. 640,
  18. };
  19. const float mean_vals[][3] =
  20. {
  21. {123.675f, 116.28f, 103.53f},
  22. {123.675f, 116.28f, 103.53f},
  23. };
  24. const float norm_vals[][3] =
  25. {
  26. {0.01712475f, 0.0175f, 0.01742919f},
  27. {0.01712475f, 0.0175f, 0.01742919f},
  28. };
  29. const char* modeltype = modeltypes[(int)modelid];
  30. int target_size = target_sizes[(int)modelid];
  31. bool use_gpu = (int)cpugpu == 1;
  32. // reload
  33. {
  34. ncnn::MutexLockGuard g(lock);
  35. if (use_gpu && ncnn::get_gpu_count() == 0)
  36. {
  37. // no gpu
  38. delete g_nanodet;
  39. g_nanodet = 0;
  40. }
  41. else
  42. {
  43. if (!g_nanodet)
  44. g_nanodet = new NanoDet;
  45. g_nanodet->load(mgr, modeltype, target_size, mean_vals[(int)modelid], norm_vals[(int)modelid], use_gpu);
  46. }
  47. }
  48. return JNI_TRUE;
  49. }

 6、jni native实现-执行人像抠图换背景

  1. JNIEXPORT jint JNICALL Java_com_yiachang_keepmemo_jni_KeepOpencvNcnn_imageQualityChange(JNIEnv *env, jclass clazz, jstring imagepath,jfloat strength)
  2. {
  3. std::vector<Object> objects;
  4. std::string path = (char *) env->GetStringUTFChars(imagepath, 0);
  5. cv::Mat m = cv::imread(path);
  6. int result = g_nanodet->imageQualityChange(m, strength);
  7. jmethodID methodID = env->GetStaticMethodID(clazz, "onExecuted", "(I)V");
  8. //调用该java方法
  9. env->CallStaticVoidMethod(clazz, methodID,result);
  10. return 0;
  11. }

 7、android 端native相关接口

  1. public class KeepOpencvNcnn
  2. {
  3. public native boolean loadModel(AssetManager mgr, int modelid, int cpugpu);
  4. public native int imageHumanMatting(String imagepath);
  5. public static native int imageQualityChange(String imagepath,float strength);
  6. static {
  7. System.loadLibrary("keepOpencvNcnn");
  8. }
  9. public static void exec(String imagepath,float strength, KoNListener listener)
  10. {
  11. mKoNListener = listener;
  12. imageQualityChange(imagepath, strength);
  13. }
  14. public static void onExecuted(int n) {
  15. if(n == -1){
  16. if(mKoNListener != null){
  17. mKoNListener.onFail();
  18. }
  19. return;
  20. }
  21. if(mKoNListener != null){
  22. mKoNListener.onFinish();
  23. }
  24. }
  25. static KoNListener mKoNListener;
  26. /**
  27. * 回调监听
  28. */
  29. public interface KoNListener {
  30. /**
  31. *
  32. */
  33. void onFinish();
  34. /**
  35. *
  36. */
  37. void onFail();
  38. }
  39. }

 以上是所有相关的代码内容了,特别要注意里面相关的图片文件路径,如果使用相关代码的话需要准备相关路径的图片,或者替换里面的图片路径。

重点摘要:

纯背景图片透明化:网上很多事针对PNG图片进行的纯背景图片透明化,以下代码可以实现jpg、png纯背景图片的透明化。

  1. cv::Mat dst = cv::Mat(img_alpha_0.rows, img_alpha_0.cols, CV_8UC4);
  2. std::vector<cv::Mat> srcChannels;
  3. std::vector<cv::Mat> dstChannels;
  4. //分离通道
  5. cv::split(img_alpha_0, srcChannels);
  6. dstChannels.push_back(srcChannels[0]);
  7. dstChannels.push_back(srcChannels[1]);
  8. dstChannels.push_back(srcChannels[2]);
  9. //添加透明度通道
  10. dstChannels.push_back(alpha);
  11. //合并通道
  12. cv::merge(dstChannels, dst);

图片画质增强:网上也有很多关于对比度、亮度、饱和度等图片画质增强的文章,这里是使用高斯和权重来实现图片画质增强的,不过以下方法可以实现图片画质调整,区间范围是[-1,1],在开放的进度范围是[0,1],也就是说android端可以调整0~1范围的进度(SeekBar),来实现图片画质范围[-1,1]的调整,当然朋友们可以使用以下代码进行调整,实现自己合适的画质区间。

  1. int NanoDet::imageQualityChange(cv::Mat& src,float strength) {
  2. Mat dstT;
  3. if (src.size().empty()) {
  4. return -1;
  5. }
  6. cv::GaussianBlur(src, dstT, cv::Size(0, 0), 9);
  7. cv::addWeighted(src, 1.5, dstT, -1+strength , 0, dstT);//0~1[-1~1]
  8. imwrite("/storage/emulated/0/DCIM/Camera/aaaactt.jpg", dstT);
  9. cv::waitKey(0);
  10. return 0;
  11. }

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/285055
推荐阅读
相关标签
  

闽ICP备14008679号