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

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

源代码如下,我们使用一个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. JDBC事务(一)

    package cn.sasa.tran01; import java.sql.Connection; import java.sql.DriverManager; import java.sql.P ...

  2. Docker:从引擎和运行框架理解Docker(3)

    Docker是GO语言编写的. 1.Docker发挥的作用: 1.快速.一致.标准化的交付应用.从开发.测试.到部署交付到成产环境都可以使用docker命令处理image到不同的环境 2.部署和扩展: ...

  3. 当业务逻辑没错,直接mapper里面出错时

    仔细检查,发现sql语句写的也没问题,但就是出错 slide_show_img_url = #{slideShowImgUrl,jdbcType=VARCHAR}, where goods_slide ...

  4. 第三章 document对象及数组

    1.数组的使用(1)声明数组var 数组名=new Array();(2)数组赋值数组名[下标]=值: 2.数组声明,分配空间,赋值同时进行var 数组名=new Array(值1,值2....)va ...

  5. word之常用功能

    0.word区域:标题栏.快速访问工具栏.功能区.功能按钮.导航窗口.编辑区.水平垂直滑动条.状态栏 1.更改office主题.文件---帐户---office主题.(传统白色.浅灰色.深灰色) 2. ...

  6. 152. Maximum Product Subarray(动态规划)

    Given an integer array nums, find the contiguous subarray within an array (containing at least one n ...

  7. 无需激活直接同步登入discuz,php代码(直接可用)

    <?php /** * 抽奖 * @param int $total */ function getReward($total=1000) { $win1 = floor((0.12*$tota ...

  8. Java IO--NIO(一)

    一.基本概念描述 1.1 I/O简介 I/O即输入输出,是计算机与外界世界的一个接口.IO操作的实际主题是操作系统.在java编程中,一般使用流的方式来处理IO,所有的IO都被视作是单个字节的移动,通 ...

  9. vue mandmobile ui实现三列列表的方法

    vue mandmobile ui实现三列列表的方法 请问这种列表的要用那个组件好呢?Cellitem 只能用到两列,这个要三列的怎么弄?mand的好像没有listview,grid组件的 问了man ...

  10. Unable to execute 'doFinal' with cipher instance

    今天项目启动后登录项目,突然爆出Unable to execute 'doFinal' with cipher instance错误.清除cookie登录测试,又不报错了,以前也见过类似问题,因为不影 ...