本系列文章仅是本人学习OpenCV过程中,针对常用的OpenCV函数的相关参数和简单用法进行记录,方便随时查询和使用。对于具体算法原理不做过多介绍。
本文C++使用的OpenCV版本是的4.5.5。
python使用的OpenCV版本是4.5.3.56
官方文档参考的是 4.6.0-dev
本文内容:
本篇文章简单介绍下数字图像处理的相关概念,为后续的内容进行铺垫。
参考博客:
- 三分钟带你快速学习RGB、HSV和HSL颜色空间
- 数字图像处理之6大颜色空间
- 百度百科_RGB颜色空间
- 图像的邻接性、连通性、区域和边界
- 【CV系列】数字图像处理基础:邻域、连通性
参考书籍:
- 冈萨雷斯-数字图像处理
一幅图像可定义为一个二维函数 f ( x , y ) f(x,y) f(x,y),其中 x x x和 y y y是空间(平面)坐标,而在任何一对空间坐标 ( x , y ) (x,y) (x,y)处的幅值 f f f称为图像在该点处的强度或者灰度。当 x x x, y y y和灰度值 f f f是有限的离散数值是,我们称该图像为数字图像。通过计算机对图像进行去除噪声、增强、复原、分割、提取特征等处理的方法和技术称为数字图像处理。
一幅图像可定义为一个二维函数 f ( x , y ) f(x,y) f(x,y)。其中 x x x为空间横坐标, y y y为空间纵坐标。起点我们通常设置为左上角。所以一幅图片我们用矩阵的形式表现出来如下图。其中 M M M是指图片的行数, N N N是指图片的列数,所以该图片是一个 M × N M \times N M×N的矩阵, f f f值都是对应坐标上的像素值。该图节选自冈萨雷斯版数字图像处理公式 2 _ 4 _ 1 2_\_4\_1 2_4_1
空间分辨率是图像中可辨别的最小细节的度量。也就是数字图像的采样分辨率。在数量上,空间分辨率可以有很多方法表示。其中每单位距离线对数和每单位距离点数(像素数)是最通用的度量。每单位距离点数是印刷和出版业中最常用的图像分辨率的度量。在美国,这一度量通常使用每英寸点数(dpi)来表示。越大的分辨率表示了图片显示的越精细。如下图显示的钟表该图片来自冈萨雷斯数字图像处理-图2.20
DPI是指每英寸的像素,也就是扫描精度。DPI越低,扫描的清晰度越低,由于受网络传输速度的影响,web上使用的图片都是72dpi,但是冲洗照片不能使用这个参数,必须是300dpi或者更高350dpi。例如要冲洗 4 × 6 4\times6 4×6英寸的照片,扫描精度必须是300dpi,那么文件尺寸应该是( 4 × 300 4\times300 4×300) × \times ×( 6 × 300 6\times300 6×300)= 1200 × 1800 1200\times1800 1200×1800像素。每单位距离点数(单位mm)同理。
灰度分辨率是指在灰度级中可分辨的最小变化。也就是数字图像的量化分辨率。基于硬件的考虑,正如前一节中提到的那样,灰度级数通常是2的整数次幂。最通用的数是8比特(表示灰度的变化范围是在0-255),在某些特殊的图像增强应用中,用16比特也是必要的。有时会发现使用10比特或12比特来数字化图像灰度级的系统,但这些系统都是特例而不是常规系统。不像空间分辨率必须以每单位距离基础才有意义,灰度分辨率指的是用于量化灰度的比特数。举例说明,下图分别显示了人头骨在不同像素级别下的图像,该图像来自于冈萨雷斯数字图像处理图片2.21。
假设坐标 ( x , y ) (x,y) (x,y)处的像素 p p p有4个与其水平和垂直的相邻像素,坐标分别为:
( x + 1 , y ) , ( x − 1 , y ) , ( x , y + 1 ) , ( x , y − 1 ) (x+1,y),(x-1,y),(x,y+1),(x,y-1) (x+1,y),(x−1,y),(x,y+1),(x,y−1)
则这组像素称为 p p p的4邻域(自然是不包括 p p p的),用 N 4 ( p ) N4(p) N4(p)表示。每个像素距 ( x , y ) (x,y) (x,y)一个单位距离,如果 ( x , y ) (x,y) (x,y)位于图像的边界上,则 p p p的某些相邻像素位于数字图像的外部。如下图所示:
p p p的4个对角相邻像素的坐标为:
( x + 1 , y + 1 ) , ( x + 1 , y − 1 ) , ( x − 1 , y + 1 ) , ( x − 1 , y − 1 ) (x+1,y+1),(x+1,y-1),(x-1,y+1),(x-1,y-1) (x+1,y+1),(x+1,y−1),(x−1,y+1),(x−1,y−1)
并用 N D ( p ) ND(p) ND(p)表示。 N 4 ( p ) N4(p) N4(p)和 N D ( p ) ND(p) ND(p)合起来称为p的8邻域,用 N 8 ( p ) N8(p) N8(p)表示。同样,这些相邻像素点是可能落在图像外边的。如下图所示:
注:以上概念为邻域,不是像素邻接。
令 V V V是用于定义邻接性的灰度值集合。灰度值集合 V V V指的是0-255的任意一个子集。
此时则有
4邻接: 像素 p p p和 q q q的灰度值属于集合 V V V,像素 q q q在 N 4 ( p ) N_4 (p) N4(p)中。例如下图
8邻接: 像素 p p p和 q q q的灰度值属于集合 V V V,像素 q q q在 N 8 ( p ) N_8 (p) N8(p)中。例如下图
m邻接: 为了解决8邻接的二义性。
下列两个条件满足其中一个即为 m m m邻接
对于 V V V假设要 p p p, q q q两点是m邻接的 则要满足下面两个条件之一即可:
1) q q q在 p p p的4邻域中。
2) q q q在 p p p的对角领域中,并且 q q q的4邻域与 p p p的4领域相交为空集(交集无点属于 V V V)。
m邻接克服了8邻接的二义性。如下图所示, V = { 1 } V=\{1\} V={1}:
注意区别邻域与邻接概念之区别。
定义:
像素 p ( x , y ) p(x,y) p(x,y)到像素 q ( s , t ) q(s,t) q(s,t)的一条通路,由一系列具有坐标 ( x 0 , y 0 ) (x_0, y_0) (x0,y0), ( x 1 , y 1 ) (x_1, y_1) (x1,y1),…, ( x i , y i ) (x_i, y_i) (xi,yi),…, ( x n , y n ) (x_n, y_n) (xn,yn)的独立像素组成。
其中, ( x , y ) = ( x 0 , y 0 ) (x,y)=(x_0, y_0) (x,y)=(x0,y0), ( x n , y n ) = ( s , t ) (x_n, y_n)=(s,t) (xn,yn)=(s,t),且 ( x i , y i ) (x_i, y_i) (xi,yi)与 ( x i − 1 , y i − 1 ) (x_{i-1}, y_{i-1}) (xi−1,yi−1)邻接, 1 ≤ i ≤ n 1\leq i\leq n 1≤i≤n, n n n为通路长度。
通路种类有:4-通路;8-通路;m-通路。
从上节的图片中可以看出, 绿色的线为像素 p ( x , y ) p(x,y) p(x,y)到像素 q ( s , t ) q(s,t) q(s,t)的一条通路,分别由 ( x 0 , y 0 ) (x_0, y_0) (x0,y0), ( x 1 , y 1 ) (x_1, y_1) (x1,y1), ( x 2 , y 2 ) (x_2, y_2) (x2,y2), ( x 3 , y 3 ) (x_3, y_3) (x3,y3)组成。其中, ( x 0 , y 0 ) (x_0, y_0) (x0,y0)是 p ( x , y ) p(x,y) p(x,y), ( x 3 , y 3 ) (x_3, y_3) (x3,y3)是 q ( s , t ) q(s,t) q(s,t)。由于像素之间是m邻接,所以该通路为m通路。
连通:通路上的所有像素灰度值满足相似准则,即 ( x i , y i ) (x_i,y_i) (xi,yi)与 ( x i − 1 , y i − 1 ) (x_{i-1},y_{i-1}) (xi−1,yi−1)邻接
令 S S S是图像中的一个像素子集。如果S中的两个像素 p p p和 q q q之间存在一个通路,那么说两个像素 p p p和 q q q在 S S S中是连通的。
对于 S S S中的任何像素 p p p, S S S中连通到该像素的像素集(注意不要重复)称为 S S S的连通分量。举一个更具体的例子:假设在 S S S中有5个像素 p 1 p1 p1、 p 2 p2 p2、 p 3 p3 p3、 p 4 p4 p4、 p 5 p5 p5。然后 p 1 − p 2 − p 3 p1-p2-p3 p1−p2−p3连通、 p 4 p4 p4, p 5 p5 p5。那么这就是三个连通分量。
如果S仅有一个连通分量,则集合S称为连通集。(S中的所有像素都在一条通路上)
令 R R R是图像中的一个像素子集。如果 R R R是连通集,则称 R R R为一个区域。两个区域 R i R_i Ri和 R j R_j Rj,如果它们联合形成一个连通集,则区域 R i R_i Ri和 R j R_j Rj称为邻接区域。不邻接的区域称为不邻接区域。
假设一幅图像包含有 K K K个不连接的区域,即 R k R_k Rk, k = 1 , 2 , 3 … , K k=1,2,3…,K k=1,2,3…,K。且它们都不接触图像的边界(进行这种假设的目的在于避免处理特殊情况。这样做不会丧失一般性,因为如果一个或多个区域接触到图像的边界,我们可简单地使用1像素宽的背景值边界来填充图像)。令 R u R_u Ru代表所有 K K K个区域的并集,并令 ( R u ) c (R_u)_c (Ru)c代表其补集。我们称 R u R_u Ru中的所有点为图像的前景,而称 ( R u ) c (R_u)_c (Ru)c中所有点为图像的背景。
区域 R R R的边界(也称为边缘或轮廓)是这样的点的集合,这些点与 R R R的补集中的点邻近。换一种方式说,一个区域的边界是该区域中至少有一个背景邻点的像素集合。这里再强调一下,我们必须指定用于定义邻接的连通性(即是几连通的)。
前述定义有时称为区域的内边界,以便于其外边界相区分,外边界对应于背景边界。在开发追踪边界的算法时这一区别很重要。这种算法为了保证结果形成一个闭合通路,通常是沿外边界确立的。
我的理解是之前的连通的概念都是为了寻找一个具有相似像素的区域(邻接方式的不同,区域不同),其中区域1,区域2和区域3相互不邻接。则根据定义,区域1,2,3的并集为前景,前景的补集(蓝色区域)为背景。前景和背景的边缘为边界。
“色彩空间”一词源于西方的“Color Space”,又称作“色域”,色彩学中,人们建立了多种色彩模型,以一维、二维、三维甚至四维空间坐标来表示某一色彩,这种坐标系统所能定义的色彩范围即色彩空间。我们经常用到的色彩空间主要有RGB, CMYK, Lab等。
RGB颜色空间以R(Red:红)、G(Green:绿)、B(Blue:蓝)三种基本色为基础,通过不同的线性叠加,可以生成大多数颜色。
优点:
RGB 颜色空间是图像处理中最基本、最常用、面向硬件的颜色空间,比较容易理解。
局限性:
参考自博客1中的内容,更细节的信息可以跳转博客1来阅读, 下面HLS, Lab,CMYK同理
在图像处理中使用较多的是 HSV 颜色空间,它可以非常直观地表达颜色的色调、鲜艳程度和明暗程度,方便进行颜色的对比。
在 HSV 颜色空间下,比 BGR 更容易跟踪某种颜色的物体,常用于分割指定颜色的物体。
HSV 表达彩色图像的方式由三个部分组成:
用下面这个圆柱体来表示 HSV 颜色空间,圆柱体的横截面可以看做是一个极坐标系 ,H 用极坐标的极角表示,S 用极坐标的极轴长度表示,V 用圆柱中轴的高度表示。
在Hue一定的情况下,饱和度S减小,就是往光谱色中添加白色,光谱色所占的比例也在减小,饱和度减为0,表示光谱色所占的比例为零,导致整个颜色呈现白色。
明度V减小,就是往光谱色中添加黑色,光谱色所占的比例也在减小,明度减为0,表示光谱色所占的比例为零,导致整个颜色呈现黑色。
HSV 的拉伸对比度增强就是对 S 和 V 两个分量进行归一化(min-max normalize)即可,H 保持不变。
HLS 和 HSV 比较类似,这里一起介绍。HLS 也有三个分量,hue(色相)、saturation(饱和度)、lightness(亮度)。具体描述如下图所示:
Lab颜色模型是由CIE(国际照明委员会)制定的一种色彩模式。自然界中任何一点色都可以在Lab空间中表达出来,它的色彩空间比RGB空间还要大。另外,这种模式是以数字化方式来描述人的视觉感应, 与设备无关,所以它弥补了RGB和CMYK模式必须依赖于设备色彩特性的不足。由于Lab的色彩空间要比RGB模式和CMYK模式的色彩空间大。这就意味着RGB以及CMYK所能描述的色彩信息在Lab空间中都能 得以影射。Lab颜色模型取坐标Lab,其中L亮度;a的正数代表红色,负端代表绿色;b的正数代表黄色, 负端代表蓝色.如下图所示:图片来自百度
CMY是工业印刷采用的颜色空间。它与RGB对应。简单的类比RGB来源于是物体发光,而CMY是依据反射光得到的。具体应用如打印机:一般采用四色墨盒,即CMY加黑色墨盒
图片来自网络
# C++ example
#include
#include
using namespace cv;
int main() {
char sImgPath[] = "./cat.jpg";
char sNameWindow[] = "BGR_img";
// BGR颜色空间
Mat img;
img = imread(sImgPath, 1);
//如果读入图像失败
if (img.empty()) {
fprintf(stderr, "Can not load image %s\n", sImgPath);
return -1;
}
namedWindow(sNameWindow);
//imshow(sNameWindow, img);
imwrite("BGR_img.jpg", img);
// HSV颜色空间
Mat HSVImg;
char sHSVNameWindow[] = "HSV_img";
cvtColor(img, HSVImg, COLOR_BGR2HSV);
namedWindow(sHSVNameWindow);
//imshow(sHSVNameWindow, HSVImg);
imwrite("HSV_img.jpg", HSVImg);
// HLS颜色空间
Mat HLSImg;
char sHLSNameWindow[] = "HLS_img";
cvtColor(img, HLSImg, COLOR_BGR2HLS);
namedWindow(sHLSNameWindow);
//imshow(sHLSNameWindow, HLSImg);
imwrite("HLS_img.jpg", HLSImg);
// Lab颜色空间
Mat LabImg;
char sLabNameWindow[] = "Lab_img";
cvtColor(img, LabImg, COLOR_BGR2Lab);
namedWindow(sLabNameWindow);
//imshow(sLabNameWindow, LabImg);
imwrite("Lab_img.jpg", LabImg);
//waitKey();
destroyAllWindows();
return 0;
}
//颜色空间的代码code类型如下所示,除上述代码外,其他类型不做展示,读者可自行尝试。
/*
cv::cvtColor()支持多种颜色空间之间的转换,其支持的转换类型和转换码如下:
1、RGB和BGR(opencv默认的彩色图像的颜色空间是BGR)颜色空间的转换
cv::COLOR_BGR2RGB
cv::COLOR_RGB2BGR
cv::COLOR_RGBA2BGRA
cv::COLOR_BGRA2RGBA
2、向RGB和BGR图像中增添alpha通道
cv::COLOR_RGB2RGBA
cv::COLOR_BGR2BGRA
3、从RGB和BGR图像中去除alpha通道
cv::COLOR_RGBA2RGB
cv::COLOR_BGRA2BGR
4、从RBG和BGR颜色空间转换到灰度空间
cv::COLOR_RGB2GRAY
cv::COLOR_BGR2GRAY
cv::COLOR_RGBA2GRAY
cv::COLOR_BGRA2GRAY
5、从灰度空间转换到RGB和BGR颜色空间
cv::COLOR_GRAY2RGB
cv::COLOR_GRAY2BGR
cv::COLOR_GRAY2RGBA
cv::COLOR_GRAY2BGRA
6、RGB和BGR颜色空间与BGR565颜色空间之间的转换
cv::COLOR_RGB2BGR565
cv::COLOR_BGR2BGR565
cv::COLOR_BGR5652RGB
cv::COLOR_BGR5652BGR
cv::COLOR_RGBA2BGR565
cv::COLOR_BGRA2BGR565
cv::COLOR_BGR5652RGBA
cv::COLOR_BGR5652BGRA
7、灰度空间域BGR565之间的转换
cv::COLOR_GRAY2BGR555
cv::COLOR_BGR5552GRAY
8、RGB和BGR颜色空间与CIE XYZ之间的转换
cv::COLOR_RGB2XYZ
cv::COLOR_BGR2XYZ
cv::COLOR_XYZ2RGB
cv::COLOR_XYZ2BGR
9、RGB和BGR颜色空间与uma色度(YCrCb空间)之间的转换
cv::COLOR_RGB2YCrCb
cv::COLOR_BGR2YCrCb
cv::COLOR_YCrCb2RGB
cv::COLOR_YCrCb2BGR
10、RGB和BGR颜色空间与HSV颜色空间之间的相互转换
cv::COLOR_RGB2HSV
cv::COLOR_BGR2HSV
cv::COLOR_HSV2RGB
cv::COLOR_HSV2BGR
11、RGB和BGR颜色空间与HLS颜色空间之间的相互转换
cv::COLOR_RGB2HLS
cv::COLOR_BGR2HLS
cv::COLOR_HLS2RGB
cv::COLOR_HLS2BGR
12、RGB和BGR颜色空间与CIE Lab颜色空间之间的相互转换
cv::COLOR_RGB2Lab
cv::COLOR_BGR2Lab
cv::COLOR_Lab2RGB
cv::COLOR_Lab2BGR
13、RGB和BGR颜色空间与CIE Luv颜色空间之间的相互转换
cv::COLOR_RGB2Luv
cv::COLOR_BGR2Luv
cv::COLOR_Luv2RGB
cv::COLOR_Luv2BGR
14、Bayer格式(raw data)向RGB或BGR颜色空间的转换
cv::COLOR_BayerBG2RGB
cv::COLOR_BayerGB2RGB
cv::COLOR_BayerRG2RGB
cv::COLOR_BayerGR2RGB
cv::COLOR_BayerBG2BGR
cv::COLOR_BayerGB2BGR
cv::COLOR_BayerRG2BGR
cv::COLOR_BayerGR2BGR
————————————————
版权声明:本文为CSDN博主「1_blue」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_51105360/article/details/113941015
针对图像分割任务,有时转换颜色空间往往更有利于分割自己想要的区域。
下面仍然以上面的猫猫为例子。
#include
#include
#include
using namespace std;
using namespace cv;
int main() {
char sImgPath[] = "./cat.jpg";
char sNameWindow[] = "BGR_img";
// BGR颜色空间
Mat img;
img = imread(sImgPath, 1);
//如果读入图像失败
if (img.empty()) {
fprintf(stderr, "Can not load image %s\n", sImgPath);
return -1;
}
// HSV颜色空间
Mat hsvImg;
char sHSVNameWindow[] = "HSV_img";
cvtColor(img, hsvImg, COLOR_BGR2HSV);
// 获取HSV中的H通道图片(色相)
Mat vImg;
vImg = Mat::zeros(hsvImg.rows, hsvImg.cols, CV_8UC1);
for (int nrow = 0; nrow < hsvImg.rows; nrow++)
{
uchar* data = vImg.ptr(nrow);
for (int ncol = 0; ncol < hsvImg.cols; ncol++)
{
Vec3i obj = hsvImg.at<Vec3b>(nrow, ncol);
data[ncol] = obj[0];
}
}
//namedWindow("V_img");
//imshow("V_img", vImg);
imwrite("V_img.jpg", vImg);
//获取灰度图像
Mat grayImg;
cvtColor(img, grayImg, COLOR_BGR2GRAY);
//namedWindow("GREAY_img");
//imshow("GREAY_img", grayImg);
imwrite("GREAY_img.jpg", grayImg);
//对灰度图像进行阈值分割
Mat grayThImg;
threshold(grayImg, grayThImg, 80, 255, THRESH_BINARY);
//namedWindow("grayThImg");
//imshow("grayThImg", grayThImg);
imwrite("grayThImg.jpg", grayThImg);
//V通道图像进行阈值分割
Mat vThImg;
threshold(vImg, vThImg, 80, 255, THRESH_BINARY);
//namedWindow("vThImg");
//imshow("vThImg", vThImg);
imwrite("vThImg.jpg", vThImg);
//waitKey();
//destroyAllWindows();
return 0;
}
从上述两种结果可以看出,我们可以通过变换不同的色彩空间,来实现获取自己想要分割阈值区域。
感谢各位读者的阅读。如果您觉得对您有帮助的话,可以给小弟一个赞。
最后,缅怀毛星云先生,感谢毛星云先生的引领,本文主要参考了毛星云先生的《OpenCV3编程入门》。