『OpenCV3』基于色彩分割图片
一、遍历图像实现色彩掩码
本节我们实现这样一个算法,我们指定某种颜色和一个阈值,根据输入图片生成一张掩码,标记符合的像素(和指定颜色的差异在阈值容忍内)。
源代码如下,我们使用一个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』基于色彩分割图片的更多相关文章
- 『OpenCV3』简单图片处理
cv2和numpy深度契合,其图片读入后就是numpy.array,只不过dtype比较不常用而已,支持全部数组方法 数组既图片 import numpy as np import cv2 img = ...
- 『OpenCV3』Harris角点特征_API调用及python手动实现
一.OpenCV接口调用示意 介绍了OpenCV3中提取图像角点特征的函数: # coding=utf- import cv2 import numpy as np '''Harris算法角点特征提取 ...
- 『OpenCV3』霍夫变换原理及实现
霍夫变换常用于检测直线特征,经扩展后的霍夫变换也可以检测其他简单的图像结构. 在霍夫变换中我们常用公式 ρ = x*cosθ + y*sinθ 表示直线,其中ρ是圆的半径(也可以理解为原点到直线的距离 ...
- 『OpenCV3』滤波器实现及使用滤波器降噪
一.滤波器实现 我们实现这样一个基于拉普拉斯算子的滤波器核心,并使用它进行滤波,这可以做到锐化图像的效果, 0 -1 0 -1 5 -1 0 -1 0 首先我们完全手动的进行滤波,依赖指针操作, vo ...
- 『OpenCV3』Mat简介
Mat属性方法介绍:OpenCV2:Mat属性type,depth,step 推荐一套OpenCV入门博客:OpenCV探索 一.Mat Mat类用于表示一个多维的单通道或者多通道的稠密数组.能够用来 ...
- 『OpenCV3』处理视频&摄像头
在opencv中,摄像头和视频文件并没有很大不同,都是一个可以read的数据源,使用cv2.VideoCapture(path).read()可以获取(flag,当前帧),对于每一帧,使用图片处理函数 ...
- 『OpenCV3』滤波器边缘检测
一.原理简介 边缘检测原理 - Sobel, Laplace, Canny算子 X方向Sobel算子 -1 -2 -1 0 0 0 1 2 1 Y方向Sobel算子 -1 0 1 -2 0 2 -1 ...
- [原创] 【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 ...
- 『计算机视觉』Mask-RCNN_推断网络其二:基于ReNet101的FPN共享网络暨TensorFlow和Keras交互简介
零.参考资料 有关FPN的介绍见『计算机视觉』FPN特征金字塔网络. 网络构架部分代码见Mask_RCNN/mrcnn/model.py中class MaskRCNN的build方法的"in ...
随机推荐
- python练习题-day9
2.写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者. def dan_index(itter): li=[] for i in range(len(itte ...
- python if 和 else
money = input("请输入你在学校有多少钱?")if int(money) > 5000: print("这个月可以潇洒潇洒了,每天可以出去吃大餐了.&q ...
- SpringBoot框架的使用
什么是SpringBoot Spring Boot是Spring社区发布的一个开源项目,旨在帮助开发者快速并且更简单的构建项目.大多数SpringBoot项目只需要很少的配置文件. SpringBoo ...
- python基础(十三) cmd命令调用
python cmd命令调用 关于python调用cmd命令: 主要介绍两种方式: 1.python的OS模块. OS模块调用CMD命令有两种方式:os.popen(),os.system(). 都是 ...
- shell bash-shell
bash shell中的命令替换,`cmd`或者$(cmd). bash shell中的变量赋值,直接name = var: (bash中的变量赋值不能中间有空格) 变量引用时,$name,如果na ...
- sitecore 数字化营销-path funnel
路径分析器是一个应用程序,允许您查看联系人在浏览网站时所采用的各种路径.您可以查看联系人在转换目标并与广告系列互动时所采用的路径,让您深入了解哪些路径为每次转化提供最佳参与价值,以及哪些路径效率较低且 ...
- C#-----中使用using详解
1.using指令 using + 命名空间名字 例:using System; using System.Data; 2.using语句 定义一个范围,在范围结束时处理对象,出了这个范 ...
- zabbix 乱码问题
一.乱码原因 查看cpu负载,中文乱码如下 这个问题是由于zabbix的web端没有中文字库,我们最需要把中文字库加上即可 二.解决zabbix乱码方法 2.1 上传字体文件到zabbix中 找到本地 ...
- 【数据使用】3k水稻数据库现成SNP的使用
---恢复内容开始--- 我们经常说幻想着使用已有数据发表高分文章,的确,这样的童话故事每天都在发生,但如何走出第一步我们很多小伙伴不清楚,那么我们就从水稻SNP数据库的使用来讲起. http://s ...
- jQuery 查找元素2
jQuery 查找元素2 :first <ul> <li>list item 1</li> <li>list item 2</li> < ...
