一、遍历图像实现色彩掩码

本节我们实现这样一个算法,我们指定某种颜色和一个阈值,根据输入图片生成一张掩码,标记符合的像素(和指定颜色的差异在阈值容忍内)。

源代码如下,我们使用一个class完成这个目标,其指定了两种构建函数,并通过逐像素扫描的形式生成掩码(process成员函数)。另外,本class做了仿函数处理(operator成员函数),类似于python中的__call__方法,可以直接调用实例像函数一样进行处理。注意迭代器的使用,需要++it而非

  1. class ColorDetector {
  2. private:
  3. int maxDist; // 允许的最小差距
  4. cv::Vec3b target; // 目标颜色
  5. cv::Mat result; // 结果Mask图像
  6.  
  7. public:
  8. // 空构造函数
  9. ColorDetector() :maxDist(100), target(0, 0, 0) {};
  10. ColorDetector(uchar blue, uchar green, uchar red, int maxDist) : maxDist(maxDist) {
  11. setTargetColor(blue, green, red);
  12. };
  13.  
  14. // 设置颜色差距阈值
  15. void setColorDistanceThreshold(int distance) {
  16. if (distance < 0)
  17. distance = 0;
  18. maxDist = distance;
  19. };
  20. // 获取颜色差距阈值
  21. int getColorDistanceThreshold() {
  22. return maxDist;
  23. };
  24.  
  25. // 设置待检测颜色
  26. void setTargetColor(uchar blue, uchar green, uchar red) {
  27. target = cv::Vec3b(blue, green, red);
  28. };
  29. void setTargetColor(cv::Vec3b color) {
  30. target = color;
  31. };
  32.  
  33. // 计算与目标颜色的差距
  34. int getDistanceToTargetColor(const cv::Vec3b& color) const {
  35. return getColorDistance(color, target);
  36. };
  37. // 计算两个颜色之间的距离
  38. int getColorDistance(const cv::Vec3b& color1, const cv::Vec3b& color2) const {
  39. return abs(color1[0] - color2[0]) +
  40. abs(color1[1] - color2[1]) +
  41. abs(color1[2] - color2[2]);
  42. };
  43. cv::Mat process(const cv::Mat &image);
  44.  
  45. // operator()使类像函数一样工作
  46. cv::Mat operator()(const cv::Mat &image) {
  47. return process(image);
  48. };
  49. };
  50.  
  51. cv::Mat ColorDetector::process(const cv::Mat &image) {
  52. // 为Mask结果申请空间
  53. result.create(image.size(), CV_8U);
  54.  
  55. cv::Mat_<cv::Vec3b>::const_iterator it = image.begin<cv::Vec3b>();
  56. cv::Mat_<cv::Vec3b>::const_iterator itend = image.end<cv::Vec3b>();
  57. cv::Mat_<uchar>::iterator itout = result.begin<uchar>();
  58.  
  59. for (; it != itend; ++it, ++itout) {
  60. if (getDistanceToTargetColor(*it) < maxDist) {
  61. *itout = 255;
  62. }
  63. else {
  64. *itout = 0;
  65. };
  66. };
  67. return result;
  68. };

两种调用方法都列举了出来:

  1. void code_3() {
  2. // 创建色彩检测器对象
  3. ColorDetector cdetect;
  4. cdetect.setTargetColor(10, 50, 10);
  5. // 读取图片
  6. cv::Mat image = cv::imread("test.jpg");
  7. // 处理图片
  8. cv::Mat result = cdetect.process(image);
  9. cv::imshow("色彩检测", result);
  10.  
  11. // 仿函数
  12. ColorDetector colordetector(230, 190, 130, 150);
  13. result = colordetector(image);
  14. cv::imshow("仿函数色彩检测", result);
  15. };

输出图像展示:

二、threshold函数

使用如下成员函数替换上面的同名成员函数,

  1. cv::Mat ColorDetector::process(const cv::Mat &image) {
  2. cv::Mat output;
  3. // output存储每个像素点(3通道)残差绝对值
  4. cv::absdiff(image, cv::Scalar(target), output);
  5. std::vector<cv::Mat> channels;
  6. cv::split(output, channels);
  7. // output存储每个位置3通道残差和
  8. output = channels[0] + channels[1] + channels[2];
  9. std::cout << output.channels() << std::endl;
  10. // 判断每个位置像素和偏差不大于阈值即为所寻点,生成掩码
  11. cv::threshold(
  12. output, // 输入
  13. output, // 输出
  14. maxDist, // 阈值,需要小与255
  15. 255, // 标记值(符合条件点)
  16. cv::THRESH_BINARY_INV // 不大于阈值点标记为标记值
  17. );
  18. return output;
  19. };

使用OpenCV的掩码生成函数threshold可以优化速度,不过由于需要一些中间过程,会消耗额外的内存,输出也可能(万一OpenCV工程师们在源码里添加了奇技淫巧呢)略有差异,

三、Mat(包含Scalar)数值运算API的优势

cv::Vec3b和cv::Scalar

我们简单的提一句cv::Vec3b和cv::Scalar的区别,两者都可以表示3通道像素的基本点,不过Vec更倾向对于原始的数据的格式化view,即和Mat耦合度不高,仅仅是个3元素数组;而Scalar更抽象倾向于表示一个像素,可以和Mat直接广播运算。

Mat的数值运算API

对比两种方式生成的残差矩阵,可以看到第一幅图中偏黑的部分在第二幅图上对应位置是特别亮的部分,联想到我们矩阵的类型是uchar,即无符号整形,存在上溢情况,可以推断API计算(即Mat的数值运算,包含上面程序中使用的加法运算符重载)出来的残差针对0~255做了截断,有效的防止了上溢:

我们将自己实现颜色差值计算的函数进行修改,添加上截断部分,

  1. // 计算两个颜色之间的距离
  2. int getColorDistance(const cv::Vec3b& color1, const cv::Vec3b& color2) const {
  3. return cv::saturate_cast<uchar>(
  4. abs(color1[0] - color2[0]) +
  5. abs(color1[1] - color2[1]) +
  6. abs(color1[2] - color2[2]));
  7. };

再查看输出的残差图像,发现和API计算结果已经一致(如下图)。

四、基于HSV色彩空间分割皮肤

原理很简单的一个例子,使用HSV色彩空间的两个通道设定阈值筛选符合的像素即可,要点:

HSV空间中的色调、饱和度两项指标可以用于分割皮肤

cv::cvtColor函数用于色彩空间转换

色调空间呈现环状(0~360),所以当max>min时,取两者之间的,当max<=min时,取小于max的和大于min的并集

色调空间在8位表示时使用0~180代替0~360

cv::inRange(img, minpixel, maxpixel, mask) 函数和threshold函数类似,生成掩码

Mat操作熟练地使用位运算可以提升程序效率,并且简化逻辑设计

函数源码见下面

  1. void detectHScolor(
  2. const cv::Mat& image, // 输入图片
  3. double minHue, double maxHue, // 色调区间
  4. double minSat, double maxSat, // 饱和度区间
  5. cv::Mat& mask // 输出掩码
  6. ) {
  7. cv::Mat hsv;
  8. cv::cvtColor(image, hsv, CV_BGR2HSV);
  9.  
  10. std::vector<cv::Mat> channels;
  11. cv::split(hsv, channels);
  12.  
  13. // 色调掩码,色调是环形的
  14. // 记录小于maxHue
  15. cv::Mat mask0;
  16. cv::threshold(
  17. channels[0],
  18. mask0,
  19. maxHue,
  20. 255,
  21. cv::THRESH_BINARY_INV
  22. );
  23. // 记录大于minHue
  24. cv::Mat mask1;
  25. cv::threshold(
  26. channels[0],
  27. mask1,
  28. maxHue,
  29. 255,
  30. cv::THRESH_BINARY
  31. );
  32. cv::Mat hueMask;
  33. if (minHue < maxHue)
  34. hueMask = mask0 & mask1;
  35. else
  36. hueMask = mask0 | mask1;
  37.  
  38. // 饱和度掩码
  39. cv::Mat satMask;
  40. cv::inRange(channels[1], minSat, maxSat, satMask);
  41.  
  42. mask = hueMask & satMask;
  43. }

调用代码:

  1. cv::Mat skin = cv::imread("skin.jfif");
  2. cv::Mat mask;
  3. detectHScolor(skin, 160, 10, 25, 166, mask);
  4. cv::Mat detected(skin.size(), CV_8UC3, cv::Scalar(0, 0, 0));
  5. skin.copyTo(detected, mask);
  6. cv::imshow("皮肤检测", detected);

效果如下,很一般(本来是找了张人脸做实验的,不过出来的图容易引起不适,虽改之 ),而且由于滤镜的关系地面呈暖色调,没有成功的剔除掉。

『OpenCV3』基于色彩分割图片的更多相关文章

  1. 『OpenCV3』简单图片处理

    cv2和numpy深度契合,其图片读入后就是numpy.array,只不过dtype比较不常用而已,支持全部数组方法 数组既图片 import numpy as np import cv2 img = ...

  2. 『OpenCV3』Harris角点特征_API调用及python手动实现

    一.OpenCV接口调用示意 介绍了OpenCV3中提取图像角点特征的函数: # coding=utf- import cv2 import numpy as np '''Harris算法角点特征提取 ...

  3. 『OpenCV3』霍夫变换原理及实现

    霍夫变换常用于检测直线特征,经扩展后的霍夫变换也可以检测其他简单的图像结构. 在霍夫变换中我们常用公式 ρ = x*cosθ + y*sinθ 表示直线,其中ρ是圆的半径(也可以理解为原点到直线的距离 ...

  4. 『OpenCV3』滤波器实现及使用滤波器降噪

    一.滤波器实现 我们实现这样一个基于拉普拉斯算子的滤波器核心,并使用它进行滤波,这可以做到锐化图像的效果, 0 -1 0 -1 5 -1 0 -1 0 首先我们完全手动的进行滤波,依赖指针操作, vo ...

  5. 『OpenCV3』Mat简介

    Mat属性方法介绍:OpenCV2:Mat属性type,depth,step 推荐一套OpenCV入门博客:OpenCV探索 一.Mat Mat类用于表示一个多维的单通道或者多通道的稠密数组.能够用来 ...

  6. 『OpenCV3』处理视频&摄像头

    在opencv中,摄像头和视频文件并没有很大不同,都是一个可以read的数据源,使用cv2.VideoCapture(path).read()可以获取(flag,当前帧),对于每一帧,使用图片处理函数 ...

  7. 『OpenCV3』滤波器边缘检测

    一.原理简介 边缘检测原理 - Sobel, Laplace, Canny算子 X方向Sobel算子 -1 -2 -1 0 0 0 1 2 1 Y方向Sobel算子 -1 0 1 -2 0 2 -1 ...

  8. [原创] 【2014.12.02更新网盘链接】基于EasySysprep4.1的 Windows 7 x86/x64 『视频』封装

    [原创] [2014.12.02更新网盘链接]基于EasySysprep4.1的 Windows 7 x86/x64 『视频』封装 joinlidong 发表于 2014-11-29 14:25:50 ...

  9. 『计算机视觉』Mask-RCNN_推断网络其二:基于ReNet101的FPN共享网络暨TensorFlow和Keras交互简介

    零.参考资料 有关FPN的介绍见『计算机视觉』FPN特征金字塔网络. 网络构架部分代码见Mask_RCNN/mrcnn/model.py中class MaskRCNN的build方法的"in ...

随机推荐

  1. springcloud第一步:创建eureka注册服务

    实现服务注册 创建EureKaserver 项目 Maven依赖 <parent> <groupId>org.springframework.boot</groupId& ...

  2. Django基本配置与URLconf

    what's the Django python的框架主要有:Django.Flask.Tornado Django是一个开放源代码的Web应用框架,由Python写成.它的主要特点是大而全,我们需要 ...

  3. java框架之Hibernate(3)-一对多和多对多关系操作

    一对多 例:一个班级可以有多个学生,而一个学生只能属于一个班级. 模型 package com.zze.bean; import java.util.HashSet; import java.util ...

  4. 详细解读html中的Map,area标签

    一.定义 定义一个客户端图像映射.图像映射(image-map)指带有可点击区域的一幅图像. 二.使用 <!--定义一个图像 他的边框为0(border) usemap(指定该图像被用作图像地图 ...

  5. Python学习之数组类型一:

    Python学习之数组类型一: Numpy中的向量与矩阵: 1.创建:  向量.矩阵均由array函数创建,区别在于向量是v=array( [逗号分隔的元素] ), 矩阵是M=array( [[ ]] ...

  6. OGG学习笔记05-OGG的版本

    刚接触OGG的时候,很容易被众多的版本搞晕,虽然官方有提供各版本对应认证OS和DB的表格. 个人认为一个比较简单的方式,是直接去edelivery.oracle.com下载OGG,选定一个大版本后,这 ...

  7. HDU 2874 Connections between cities(LCA Tarjan)

    Connections between cities [题目链接]Connections between cities [题目类型]LCA Tarjan &题意: 输入一个森林,总节点不超过N ...

  8. lua语言中的假

    [1]测试及结论 (1)代码 local var_false = false local var_nil = nil if var_zero then print('var_zero : true') ...

  9. django+nginx+python3 生产环境部署

    一.安装python基础环境 1.安装各类基础模块 yum install  gcc-c++ wget openssl-devel bzip2-devel expat-devel gdbm-devel ...

  10. jQuery循环

    1.循环数组.对象: .$each(数组/对象,结果函数),即.$(arr,function(x,y) {...} ) 如果是数组,则函数中的x表示索引,y表示索引对应的值,只传递一个参数的话则表示索 ...