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

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

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

class ColorDetector {
private:
int maxDist; // 允许的最小差距
cv::Vec3b target; // 目标颜色
cv::Mat result; // 结果Mask图像 public:
// 空构造函数
ColorDetector() :maxDist(100), target(0, 0, 0) {};
ColorDetector(uchar blue, uchar green, uchar red, int maxDist) : maxDist(maxDist) {
setTargetColor(blue, green, red);
}; // 设置颜色差距阈值
void setColorDistanceThreshold(int distance) {
if (distance < 0)
distance = 0;
maxDist = distance;
};
// 获取颜色差距阈值
int getColorDistanceThreshold() {
return maxDist;
}; // 设置待检测颜色
void setTargetColor(uchar blue, uchar green, uchar red) {
target = cv::Vec3b(blue, green, red);
};
void setTargetColor(cv::Vec3b color) {
target = color;
}; // 计算与目标颜色的差距
int getDistanceToTargetColor(const cv::Vec3b& color) const {
return getColorDistance(color, target);
};
// 计算两个颜色之间的距离
int getColorDistance(const cv::Vec3b& color1, const cv::Vec3b& color2) const {
return abs(color1[0] - color2[0]) +
abs(color1[1] - color2[1]) +
abs(color1[2] - color2[2]);
};
cv::Mat process(const cv::Mat &image); // operator()使类像函数一样工作
cv::Mat operator()(const cv::Mat &image) {
return process(image);
};
}; cv::Mat ColorDetector::process(const cv::Mat &image) {
// 为Mask结果申请空间
result.create(image.size(), CV_8U); cv::Mat_<cv::Vec3b>::const_iterator it = image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::const_iterator itend = image.end<cv::Vec3b>();
cv::Mat_<uchar>::iterator itout = result.begin<uchar>(); for (; it != itend; ++it, ++itout) {
if (getDistanceToTargetColor(*it) < maxDist) {
*itout = 255;
}
else {
*itout = 0;
};
};
return result;
};

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

void code_3() {
// 创建色彩检测器对象
ColorDetector cdetect;
cdetect.setTargetColor(10, 50, 10);
// 读取图片
cv::Mat image = cv::imread("test.jpg");
// 处理图片
cv::Mat result = cdetect.process(image);
cv::imshow("色彩检测", result); // 仿函数
ColorDetector colordetector(230, 190, 130, 150);
result = colordetector(image);
cv::imshow("仿函数色彩检测", result);
};

输出图像展示:

二、threshold函数

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

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

使用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做了截断,有效的防止了上溢:

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

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

再查看输出的残差图像,发现和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操作熟练地使用位运算可以提升程序效率,并且简化逻辑设计

函数源码见下面

void detectHScolor(
const cv::Mat& image, // 输入图片
double minHue, double maxHue, // 色调区间
double minSat, double maxSat, // 饱和度区间
cv::Mat& mask // 输出掩码
) {
cv::Mat hsv;
cv::cvtColor(image, hsv, CV_BGR2HSV); std::vector<cv::Mat> channels;
cv::split(hsv, channels); // 色调掩码,色调是环形的
// 记录小于maxHue
cv::Mat mask0;
cv::threshold(
channels[0],
mask0,
maxHue,
255,
cv::THRESH_BINARY_INV
);
// 记录大于minHue
cv::Mat mask1;
cv::threshold(
channels[0],
mask1,
maxHue,
255,
cv::THRESH_BINARY
);
cv::Mat hueMask;
if (minHue < maxHue)
hueMask = mask0 & mask1;
else
hueMask = mask0 | mask1; // 饱和度掩码
cv::Mat satMask;
cv::inRange(channels[1], minSat, maxSat, satMask); mask = hueMask & satMask;
}

调用代码:

	cv::Mat skin = cv::imread("skin.jfif");
cv::Mat mask;
detectHScolor(skin, 160, 10, 25, 166, mask);
cv::Mat detected(skin.size(), CV_8UC3, cv::Scalar(0, 0, 0));
skin.copyTo(detected, mask);
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. SyntaxError:unexpected EOF while parsing(<string,line 0>)

    在python+Django中出现报错:(上图) 经断点发现:python内置函数eval在处理空字符串时会返回EOF错误,判断后解决

  2. Interllij IDEA中启动web项目

    1.在IDEA中打开你的Web应用,点击一下绿色三角形左边的框框,然后在弹出框上选择Edit Configurations,会弹出一个配置面板. 2.在弹出的面板中我们点击Defaults,然后找到T ...

  3. 5、 LwIP协议栈规范翻译——操作系统仿真层

    为了使lwIP可移植,操作系统特定的函数调用和数据结构不直接在协议的代码中使用.相反,当需要这样的函数调用和数据结构时,直接使用操作系统仿真层. 操作系统仿真层为操作系统服务提供统一的接口,如定时器, ...

  4. 4、Flutter 采坑记录篇二_依赖库不兼容

    1.报错信息 Because every version of flutter_test from sdk depends on package_resolver 1.0.4 which depend ...

  5. java框架之Spring(4)-Spring整合Hibernate和Struts2

    准备 导包 Struts2 导入 Struts2 zip 包解压目录下 'apps/struts-blank.war' 中所有 jar 包,如下: asm-3.3.jar asm-commons-3. ...

  6. SybaseIQ上SQL基本使用

    锁定: sp_iqlocks 踢人: 存储过程查找: sybase central里面查询的命令(查找过滤器例如:%table%),Sybase Central/Sybase IQ 15/服务器/xx ...

  7. PHP GZ压缩与解压

    /*将字符串添加至GZ文件*/ function gz_str($str,$gz_name){ $fp = gzopen ($gz_name, 'w9'); gzwrite ($fp, $str); ...

  8. PHP----------安装包lnmp1.3-full安装的lnmp环境,如何安装PHP扩展

    1. 如果已经安装LNMP套件,请按以下步骤处理 a. 跳转到fileinfo源代码目录` cd /root/downloads/lnmp1.2-full/src/php-7.0.7/ext/file ...

  9. vim 命令学习(高级篇)

    [1]打开文件方式 (1)vim +n filename 作用:打开文件,并定位到第n行 例如:vim +103 2019-02-26-errorrepeat.txt 效果:打开2019-02-26- ...

  10. Windwos Live Writer插件指南

    Windows Live Writer 即(WLW) 是一个免费的桌面应用程序,可以用于发布博客. 官网下载地址:https://www.microsoft.com/zh-CN/download/de ...