• [其他] OpenCV20---图像梯度
    图像梯度图像梯度计算的是图像变化的速度。对于图像的边缘部分,其灰度值变化较大,梯度值也较大;相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值也较小。一般情况下,图像的梯度计算是图像的边缘信息。其实梯度就是导数,但是图像梯度一般通过计算像素值的差来得到梯度的近似值,也可以说是近似导数。该导数可以用微积分来表示。在微积分中,一维函数的一阶微分的基本定义是这样的:而图像是一个二维函数f(x,y),其微分当然就是偏微分。因此有:因为图像是一个离散的二维函数,ϵ不能无限小,我们的图像是按照像素来离散的,最小的ϵ就是1像素。因此,上面的图像微分又变成了如下的形式(ϵ=1):这分别是图像在(x, y)点处x方向和y方向上的梯度,从上面的表达式可以看出来,图像的梯度相当于2个相邻像素之间的差值。那么,这个梯度(或者说灰度值的变化率)如何增强图像的清晰度呢?我们先考虑下x方向,选取某个像素,假设其像素值是100,沿x方向的相邻像素分别是90,90,90,则根据上面的计算其x方向梯度分别是10,0,0。这里只取变化率的绝对值,表明变化的大小即可。我们看到,100和90之间亮度相差10,并不是很明显,与一大群90的连续灰度值在一起,轮廓必然是模糊的。我们注意到,如果相邻像素灰度值有变化,那么梯度就有值,如果相邻像素灰度值没有变化,那么梯度就为0。如果我们把梯度值与对应的像素相加,那么灰度值没有变化的,像素值不变,而有梯度值的,灰度值变大了。我们看到,相加后的新图像,原图像像素点100与90亮度只相差10,现在是110与90,亮度相差20了,对比度显然增强了,尤其是图像中物体的轮廓和边缘,与背景大大加强了区别,这就是用梯度来增强图像的原理。上面只是说了x方向,y方向是一样的。那么能否将x方向和y方向的梯度结合起来呢?当然是可以的。x方向和y方向上的梯度可以用如下式子表示在一起:这里又是平方,又是开方的,计算量比较大,于是一般用绝对值来近似平方和平方根的操作,来降低计算量:原理了解后,我们来了解一些OpenCV提供了哪些梯度滤波器?在OpenCV中,它给我们提供了三种不同的梯度滤波器,或者说高通滤波器:Sobel,Scharr 和Laplacian。什么叫高通呢?其实就是和图像模糊相反。图像模糊是让低频通过,阻挡高频,这样就可以去除噪点,让锐利的边缘变平滑。高通滤波器就是让高频通过,阻挡低频,可以让边缘更加明显,增强图像。来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • [其他] OpenCV(19)---通用形态学函数
    通用形态学函数上篇博文,我们介绍了形态学的基础腐蚀与膨胀操作,而将腐蚀与膨胀结合起来进行组合,我们就能实现开运算,闭运算等复杂的形态学运算。在OpenCV中,它给我们提供的通用形态学函数为cv2.morphologyEx(),其完整定义如下:def morphologyEx(src, op, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None): 复制代码这些参数基本前面都介绍过,不过有一点需要说明,src原始图像必须是CV_8U,CV_16U,CV_16S,CV_32F,CV_64F中的一种。当然,这里面还有一个陌生的参数就是op,它就是各种形态学的类别,具体类别如表所示:类型说明意义操作cv2.MORPH_ERODE腐蚀腐蚀erode()cv2.MORPH_DILATE膨胀膨胀dilate()cv2.MORPH_OPEN开运算先腐蚀后膨胀dilate(erode())cv2.MORPH_CLOSE闭运算先膨胀后腐蚀erode(dilate())cv2.MORPH_GRADIENT形态学梯度运算膨胀图减腐蚀图dilate()-erode()cv2.MORPH_TOPHAT顶帽运算原始图像减开运算所得图像src-open()cv2.MORPH_BLACKHAT黑帽运算闭运算所得图像减原始图像close()-srccv2.MORPH_HITMISS击中击不中前景背景腐蚀运算的交集。仅仅支持CV8UC1二进制图像intersection(erode(src),erode(src1))开运算如上表所示,开运算是将原图像腐蚀,再对其进行膨胀操作。主要用于去噪,计数等。去噪我们已经通过上面的腐蚀操作就可以完成,下面我们来实现有趣的计数操作。import cv2 import numpy as np img = cv2.imread("open.jpg",cv2.IMREAD_UNCHANGED) kernel = np.ones((9,9), np.float32) result = cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel,iterations=5) cv2.imshow("img", img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,我们能将不同区域划分开来,效果如下:闭运算闭运算是先膨胀后腐蚀的运算,它有助于关闭前景物体内部的小孔,或去除物体上的小黑点,还可以将不同的前景图像进行连接。下面,我们就将上图进行连接。import cv2 import numpy as np img = cv2.imread("close.jpg", cv2.IMREAD_UNCHANGED) kernel = np.ones((10, 10), np.float32) result = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations=7) cv2.imshow("img", img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,两个方块就连接为一个整体了,效果如下所示:形态学梯度运算形态学梯度运算是用图像膨胀后的图像减去腐蚀图像的运算,该操作可以获取原始图像中的前景图像的边缘。我们还是用上篇膨胀的图来测试,代码如下:import cv2 import numpy as np img = cv2.imread("8.jpg", cv2.IMREAD_UNCHANGED) kernel = np.ones((5, 5), np.float32) result = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel,iterations=2) cv2.imshow("img", img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,我们的图像就中空了,效果如下:顶帽运算顶帽运算是用原始图像减去其开运算图像的操作。它能够获取图像的噪声信息,或者得到比原图像的边缘更亮的边缘信息。也就是获取上图中的白色线条,具体代码如下:import cv2 import numpy as np img = cv2.imread("8.jpg", cv2.IMREAD_UNCHANGED) kernel = np.ones((5, 5), np.float32) result = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel,iterations=2) cv2.imshow("img", img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,效果如下:黑帽运算黑帽运算是用闭运算图像减去原始图像的操作。它能够获取内部的小孔,或前景色中的小黑点,亦或者得到比原始图像的边缘更暗的边缘部分。这里,我们用前面的人物图像,代码如下:import cv2 import numpy as np img = cv2.imread("4.jpg", cv2.IMREAD_UNCHANGED) kernel = np.ones((5, 5), np.float32) result = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel,iterations=2) cv2.imshow("img", img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,效果如下:结构元函数前面我们介绍过,结构元可以自定义,也可以通过cv2.getStructuringElement()函数生成。这里,我们来看看其完整的定义:def getStructuringElement(shape, ksize, anchor=None): 复制代码shape:形状类型,取值如下表:类型意义cv2.MORPH_RECT矩形结构元,所有元素值为1cv2.MORPH_CROSS十字形结构元,对角线元素值为1cv2.MORPH_ELLIPSE椭圆形结构元素ksize:结构元的大小anchor:结构元的锚点位置,默认值(-1,1),是形状的中心。只有十字星型的形状与锚点位置紧密联系。在其他情况下,锚点位置仅用于形态学运算结果的调整。下面,我们将这三种形状类型都实现一遍,具体代码如下:import cv2 img = cv2.imread("open.jpg", cv2.IMREAD_UNCHANGED) kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT,(50,50)) kernel2 = cv2.getStructuringElement(cv2.MORPH_CROSS,(50,50)) kernel3 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(50,50)) result1 = cv2.dilate(img,kernel1) result2 = cv2.dilate(img,kernel2) result3 = cv2.dilate(img,kernel3) cv2.imshow("img", img) cv2.imshow("result1", result1) cv2.imshow("result2", result2) cv2.imshow("result3", result3) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,效果如下所示:来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • [其他] OpenCV(18)---腐蚀与膨胀
    要了解腐蚀之前,我们需要了解一个概念:形态学。 形态学,又名数学形态学(Mathematical Morphology),是图像处理过程中一个非常重要的研究方向。形态学主要从图像内提取分量信息,该分量信息通常对于表达和描绘图像的形状具有重要的意义,通常是图像理解时所使用的最本质的形状特征。 例如,在识别手写数字时,能够通过形态学运算得到其骨架信息,在具体的识别时,仅针对其骨架进行运算即可。形态学处理在视觉检测,文字识别,医学图像处理,图像压缩编码等领域都有非常重要的应用。 形态学操作主要包含:腐蚀,膨胀,开运算,闭运算,形态学梯度运算,顶帽运算,黑帽运算等操作。腐蚀操作与膨胀操作是形态学的运算基础,将腐蚀与膨胀结合,就可以实现开运算,闭运算,形态学梯度等不同形式的运算。 所以本篇博文将重点讲解腐蚀与膨胀。 腐蚀是最基本的形态学操作之一,它能够将图像的边界点消除,使图像沿着边界向内收缩,也可以将小于指定结构体元素的部分去除。腐蚀主要用来“收缩”或者“细化”二值图像中的前景,借此实现去噪声,元素分割等功能。 在腐蚀的过程中,通常使用一个结构元来逐个像素地扫描要被腐蚀的图像,并根据结构元和被腐蚀的图像的关系来确定腐蚀结果。 首先,我们来看一张腐蚀处理图: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/102032saztjpf3yycvrhah.png) (1)表示要被腐蚀的图像 (2)结构元 (3)橙色数字是结构元在遍历图像时,结构元完全位于前景对象内部时的3个全部可能的位置;此时,结构元分别位于img\[2,1\],img\[2,2\],img\[2,3\]处。 (4)腐蚀结果result,即在结构元完全位于前景图像中时,将其中心点所对应的result中的像素点的值置为1;当结构元不完全位于前景图像中时,将其中心点对应的result中的像素点置为0。(按位与) 在OpenCV中,使用函数cv2.erode()实现腐蚀操作,其完整定义如下: ``` def erode(src, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None): ``` src:原始图像 kernel:结构元,可以自定义,也可以通过函数cv2.getStructuringElement()生成 iterations:腐蚀操作迭代的次数,默认为1,即只进行一次操作 至于其他参数以及取值,前面博文都有介绍,这里不在赘述。 下面,我们使用该函数来测试一下腐蚀的操作: ``` import cv2 import numpy as np img = cv2.imread("8.jpg",cv2.IMREAD_UNCHANGED) kernel = np.ones((9,9), np.float32) result = cv2.erode(img,kernel) cv2.imshow("img", img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() ``` 运行之后,我们得到的效果对比图如下: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/102100ujyqrr3ni1jyfzwb.png) 可以看到,腐蚀操作将原始图像内的毛刺给腐蚀掉了,如果想腐蚀的更严重,可以设置iterations参数的值。 膨胀与腐蚀正好相反,膨胀能对图像的边界进行扩张。膨胀操作将与当前的对象(前景)接触到的背景点合并到当前对象内,从而实现将图像边界点向外扩张。如果图像内的两个对象距离较近,可能在膨胀后连接到一起。 膨胀操作对填补图像分割后图像内所存在的空白相当有帮助。同样的,膨胀也需要一个结构元进行操作。下面,我们还是用矩阵来讲解膨胀的原理。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/102118xwdqbxezlglne90u.png) (1)待膨胀的原图 (2)结构元 (3)橙色部分是结构元遍历原图时,结构元中心像素点位于\[1,1\],img\[3,3\]时,与前景色存在重合像素点的两种情况,实际上共有9个这样的与前景对象重合的可能位置。结构元中心分别位于img\[1,1\],img\[1,2\],img\[1,3\],img\[2,1\],img\[2,2\],img\[2,3\],img\[3,1\],img\[3,2\],img\[3,3\]。 (4)膨胀后的结果图像result,在结构元内,当任意一个像素点与前景对象重合时,其中心点所对应的膨胀结果图像内的像素点的值为1;当结构元与前景对象完全无重合时,其中心点对应的膨胀结果图像内像素点的值为0。(按位或) 在OpenCV中,它给我们提供cv2.dilate()实现对图像的膨胀操作。其完整定义如下: ``` def dilate(src, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None): 复制代码 ``` src:原始图像 kernel:结构元 其他参数与前文一致,不在赘述。下面,我们使用该函数测试膨胀的效果,具体代码如下所示: ``` import cv2 import numpy as np img = cv2.imread("8.jpg",cv2.IMREAD_UNCHANGED) kernel = np.ones((9,9), np.float32) result = cv2.dilate(img,kernel) cv2.imshow("img", img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码 ``` 运行之后,效果如下所示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/102146s1ps90pzc6wlxuqo.png) 可以看到,图像的头像膨胀之后与身体连接到了一起,同时旁边的线条也被加粗。如果想膨胀的更严重,可以修改iterations参数的值。
  • [其他] OpenCV(17)---图像平滑处理(2)
    什么是图像平滑处理在尽量保留图像原有信息的情况下,过滤掉图像内部的噪声,这一过程我们称之为图像的平滑处理,所得到的图像称为平滑图像。那么什么是图像的噪声呢?图像的噪声就是图像中与周围像素点差异较大的像素点。噪声的处理就是将其更改为临近像素点的近似值,使图像更平滑。图像平滑处理的噪声取值的方式有以下6种:(1)均值滤波(2)方框滤波(3)高斯滤波(4)中值滤波(5)双边滤波(6)2D卷积(自定义滤波)中值滤波中值滤波与前面的三种滤波都不同,它不在采用加权求均值的方式计算滤波结果,而是用邻域内所有像素值的中间值来代替当前像素点的像素值。简单点说,就是取当前像素点及其周围临近像素点的像素值,将这些值进行排序后,取中间位置的像素值作为当前位置的像素值。在OpenCV中,它提供给我们cv2.medianBlur()函数来进行中值滤波,其完整定义如下:def medianBlur(src, ksize, dst=None): 复制代码src:原始图像kszie:滤波核的大小参数就两个,下面我们来用代码测试一下:import cv2 img = cv2.imread("5.jpg") result = cv2.medianBlur(img, 3) cv2.imshow("img", img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,显示效果如下:可以看到,这里我们将脸上的红点去掉了。需要特别注意的是,滤波核的大小必须是奇数,矩阵中心点向外衍生必然是奇数,不信可以随便矩阵取一点试试。双边滤波双边滤波是综合考虑空间信息和色彩信息的滤波方式,在滤波的过程中能够有效地保护图像内的边缘信息。前面滤波方式基本只考虑了空间的权重信息,这种情况计算起来比较方便,但是边缘信息的处理上存在较大问题。而双边滤波在处理边缘时,与当前点色彩相近的像素点给与较大的权重值,而与当前像素点色彩差别大的会给较小的权重,这样就保护了边缘信息。简单点概括,双边滤波在计算某一个像素点的新值时,不仅考虑距离信息,还考虑色彩信息。双边滤波即能有效地去除噪声,又能很好地保护边缘信息。在OpenCV中,它给我们提供cv2.bilateralFilter()函数来实现,其完整定义如下:def bilateralFilter(src, d, sigmaColor, sigmaSpace, dst=None, borderType=None): 复制代码src:原始图像d:在滤波时选取的空间距离参数,这里表示以当前像素点为中心点的直径。如果该值为非正数,则会从参数sigmaSpace计算得到。如果滤波空间较大,比如d>5,则速度较慢。因此,在实际的应用中,推荐d=5。对于噪声较大的离线滤波,可以选择d=9。sigmaColor:在滤波处理时,选择的颜色范围,该值决定了周围哪些像素点能够参与到滤波中来。与当前像素点的像素值差值小于sigmaColor的像素点,能够参与到当前的滤波中。该值越大,就说明周围有越多的像素点可以参与到运算中。该值为0时,滤波失去意义;该值为255,指定直径内的所有点都能够参与运算。sigmaSpace:坐标空间中的sigma值。它的值越大,说明有越多的点能够参与到滤波计算中来。当d>0时,无论sigmaSpace的值如何,d都指定邻域大小;否则,d域sigmaSpace的值成比例。为了简单起见,博主这里将两个sigmaColor与sigmaSpace值设置为相同的。如果它们的值比较小,比如小于10,滤波的效果不太明显;如果它们的值较大,比如大于150,则滤波效果会比较明显。代码如下所示:import cv2 img = cv2.imread("5.jpg") result = cv2.bilateralFilter(img,25,50,50) cv2.imshow("img", img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,显示效果如下所示:2D卷积在OpenCV中,除了提供上面这些常用的滤波方式之外,还允许用户自定义卷积核实现卷积操作。这个函数是cv2.Filter2D(),其完整定义如下:def filter2D(src, ddepth, kernel, dst=None, anchor=None, delta=None, borderType=None): 复制代码src:原始图像ddepth:处理结果图像的深度,-1与原图像一致。kernel:卷积核,是一个单通道数组。如果想在处理彩色图像时,让每个通道使用不同的核,则必须将彩色图像分解后使用不同的核完成。delta:修正值,可选参数。如果该值存在,会在基础滤波的结果上加上该值作为最终的滤波结果。下面,我们来使用这个函数看看效果,具体代码如下所示:import cv2 import numpy as np img = cv2.imread("5.jpg") kernel = np.ones((9,9), np.float32) / 81 result = cv2.filter2D(img, -1, kernel) cv2.imshow("img", img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,效果如下所示:来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • [其他] OpenCV(17)---图像平滑处理
    什么是图像平滑处理在尽量保留图像原有信息的情况下,过滤掉图像内部的噪声,这一过程我们称之为图像的平滑处理,所得到的图像称为平滑图像。那么什么是图像的噪声呢?图像的噪声就是图像中与周围像素点差异较大的像素点。噪声的处理就是将其更改为临近像素点的近似值,使图像更平滑。图像平滑处理的噪声取值的方式有以下6种:(1)均值滤波(2)方框滤波(3)高斯滤波(4)中值滤波(5)双边滤波(6)2D卷积(自定义滤波)均值滤波均值滤波是指用当前像素点周围N*N个像素点的均值来代替当前像素值。使用该方法遍历处理图像内的每一个像素点,即可完成整幅图像的均值滤波。在进行均值滤波处理时,我们需要考虑对周围多少个像素点取平均值。通常情况下,我们会以当前像素点为中心,对行数和列数相等的一块区域内的所有像素点取平均值。但是边缘像素点可能不能这样做,毕竟比如左上角的像素点是没有左上像素点的,这个时候我们常常会取图像内存在的周围邻域点的平均值。在OpenCV中,它给我们提供的均值滤波函数为cv2.blur(),其完整定义如下:def blur(src, ksize, dst=None, anchor=None, borderType=None):  src:原始图像kszie:滤波中心的大小,也就是取平均值的周围像素点的高度与宽度,比如(5,5),就是取5*5邻域像素点均值作为结果。anchor:锚点,其默认值为(-1,1),表示当前计算均值的点位于核的中心点位置。一般使用默认值即可。borderType:边界样式,该值决定了以何种方式处理边界,一般情况下不需要更改。了解了该函数的定义,下面我们简单的来完成一个去噪图像,具体代码如下所示:import cv2 img = cv2.imread("5.jpg") result_5img = cv2.blur(img, (5, 5)) result_30img= cv2.blur(img, (30, 30)) cv2.imshow("img", img) cv2.imshow("result_5img", result_5img) cv2.imshow("result_30img", result_30img) cv2.waitKey() cv2.destroyAllWindows() 运行之后,效果如下所示:从上图可以看出来,使用(5,5)卷积进行均值滤波处理后图像虽然模糊,但还可以辨认。而使用(30,30)卷积进行均值滤波,图像失真非常严重。所以,我们可以得出来,卷积核越大,去噪效果越好,花费的时间越长,同时图像失真也越严重。而实际的处理中,我们需要在失真与去噪之间取得平衡,选取合适的卷积大小。方框滤波方框滤波与均值滤波的不同之处在于,方框滤波不会计算像素均值,它可以自由选择是否对均值滤波的结果进行归一化,即可以自由选择滤波结果是邻域像素值之和的平均值,还是邻域像素值之和。在OpenCV中,它提供cv2.boxFilter()函数来实现方框滤波,其完整定义如下:def boxFilter(src, ddepth, ksize, dst=None, anchor=None, normalize=None, borderType=None): 复制代码src:原始图像ddepth:处理结果图像的图像深度,一般使用-1表示与原图像使用相同的图像深度ksize:滤波核心的大小normalize:是否在滤波时进行归一化处理。当它为1时,表示要进行归一化处理,也就是邻域像素值的和除以面积,比如(3,3),公式如下:当它为0时,表示不需要进行归一化处理,直接使用邻域像素值的和。下面,我们来用程序分别实现归一化与不归一化的效果,代码如下:import cv2 img = cv2.imread("5.jpg") result1 = cv2.boxFilter(img, -1, (5, 5)) result2 = cv2.boxFilter(img, -1, (30, 30)) result3 = cv2.boxFilter(img, -1, (2, 2),normalize=0) cv2.imshow("img", img) cv2.imshow("result1", result1) cv2.imshow("result2", result2) cv2.imshow("result3", result3) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,显示的效果如下所示:可以看到,左下角不需要归一化处理,这里只取(2,2),如果你取大了,可以试试。因为范围大了,和一般都会大于255,那么就会造成图像全是白色。高斯滤波在进行均值滤波与方框滤波时,其邻域内每个像素的权重是相等的。而高斯滤波会将中心点的权重加大,远离中心点的权重减小,以此来计算邻域内各个像素值不同权重的和。在OpenCV中,它给我们提供cv2.GaussianBlur()函数进行高斯滤波,其完整定义如下:def GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None): 复制代码src:原始图像ksize:滤波核的大小sigmaX:卷积和在水平方向上(X轴方向)的标准差,其控制的是权重比例sigmaY:卷积和在垂直方向上(Y轴方向)的标准差,也是控制的是权重比例。如果它为0,只采用sigmaX的值,如果sigmaX与sigmaY都是0,则通过ksize.width和ksize.height计算得到(可选参数)下面,我们来使用高斯滤波看看效果,代码如下所示:import cv2 img = cv2.imread("5.jpg") result = cv2.GaussianBlur(img, (3, 3), 0, 0) cv2.imshow("img", img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,效果如下所示:来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • [其他] OpenCV(16)---自适应阈值处理与Otsu处理
    对于色彩均衡的图像来说,直接使用一个阈值就能完成对图像的阈值化处理。但是,有时候图像的色彩是不均衡的,此时如果只用一个阈值,就无法得到清晰有效的阈值分割的图像,所以,我们需要采用自适应阈值处理。 简单的说,自适应阈值处理是通过计算每个像素点轴为临近区域的加权平均值获得阈值,并使用该阈值对当前像素点进行处理。 其优点是能够更好的处理明暗差异较大的图像。 在OpenCV中,它给我们提供了cv2.adaptiveThreshold()函数来实现自适应阈值处理,定义如下: ``` def adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst=None): ``` src:需要处理的原始图像 maxValue:代表最大值 adaptiveMethod:自适应的方法 thresholdType:代表阈值处理方式,该值必须是cv2.THRESH\_BINARY或者cv2.THRESH\_BINARY\_INV中的一个 blockSize:块的大小,表示一个像素在计算其阈值时所使用的邻域尺寸,通常为3,5,7等 C:常量 通过参数,我们可以肯定在处理图像的时候,我们需要提供一个自定义的阈值处理方法。而OpenCV给我们提供了两种:cv2.ADAPTIVE\_THRESH\_MEAN\_C与cv2.ADAPTIVE\_THRESH\_GAUSSINA\_C。它们都是逐个像素的计算自适应阈值,自适应阈值等于每个像素由参数blockSize所指定的邻域的加权平均值减去常量C。 其中,两种不同的方法计算邻域的加权平均值不同: (1)cv2.ADAPTIVE\_THRESH\_MEAN\_C:邻域所有像素点的权重值是一致的 (2)cv2.ADAPTIVE\_THRESH\_GAUSSINA\_C:与邻域各个像素点到中心点的距离有关,通过高斯方程得到各个点的权重值 下面我们分别实现看看其效果如何,具体代码如下: ``` import cv2 img = cv2.imread("4.jpg", 0) t, result_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) atmc_img=cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,5,3) atgc_im=cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,5,3) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.imshow("atmc_img", atmc_img) cv2.imshow("atgc_im", atgc_im) cv2.waitKey() cv2.destroyAllWindows() ``` ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/1005047wpqcwbcpcrze1ah.png) 在使用函数cv2.threshold()函数进行阈值处理时,需要一个自定义的阈值。通常情况下,图像是色彩均衡的,这个时候,直接设置阈值处理是比较合适的。 但是实际处理的图像往往更加的复杂,不太可能直接观察出阈值,如果一个个取尝试,无疑是一个巨大的工作量。 而Otsu方法能够根据当前图像给出的最佳的类间分割阈值。简单的说,Otsu方法会遍历所有可能阈值,从而找到最佳的阈值。 在cv2.threshold()函数中,它给我们提供了一个type参数,传递给它cv2.THRESH\_OTSU,即可实现Otsu方法的阈值分割。 > 需要注意的是,在使用Otsu方法时,要把阈值设为0。此时的cv2.threshold()会自动寻找最优阈值,并将该阈值返回。如上面代码中有个t,返回的就是阈值。 下面,我们来通过Otsu处理实现,具体代码如下所示: ``` import cv2 img = cv2.imread("4.jpg", 0) t1, result_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) t2, otsu_img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU) cv2.imshow("img", img) cv2.imshow("result_img", result_img) print(t2) cv2.imshow("otsu_img", otsu_img) cv2.waitKey() cv2.destroyAllWindows() 复制代码 ``` 运行之后,效果如下所示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/10052805pxt9tb1oew6n5a.png) 可以看到,该图像最佳的二值化阈值处理阈值是145。
  • [其他] OpenCV(15)---常规5大阈值处理
    阈值处理是剔除原图像中像素高于或者低于一定值的像素点。例如将一个灰度图像中大于200的像素点统一设置为255,这个就是阈值处理。或者说将所有低于200的像素点设置为0,也可以叫做阈值处理,两者结合处理后,图像就变为二值图像了。 在OpenCV中,我们使用cv2.threshold()函数进行阈值处理,它的定义如下所示: ``` def threshold(src, thresh, maxval, type, dst=None): ``` src:需要进行阈值处理的原始图像 thresh:需要设定的阈值 maxval:当type为THRESH\_BINARY或者THRESH\_BINARY\_INV类型时,需要设定的最大值。 type:阈值的类型,如下表所示。 类型 含义 cv2.THRESH\_BINARY ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/095952yxxj4rpllnhc6b69.png) cv2.THRESH\_BINARY\_INV ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/100009pjs9luwtmiw0vnbk.png) cv2.THRESH\_TRUNC ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/1000248jxyvv9cby3favpc.png) cv2.THRESH\_TOZERO\_INV ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/100042hhu0iddofhlvtxjy.png) cv2.THRESH\_TOZERO ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/100058fms5o8pghzvcyunt.png) cv2.THRESH\_MASK 掩码 cv2.THRESH\_OTSU 标记,使用Otsu算法时的可选阈值参数 cv2.THRESH\_TRIANGLE 标记,使用Triangle算法时的可选阈值参数 顾名思义,二值化阈值处理,会将原始图像变更为仅有2个值的二值图像,也就是cv2.THRESH\_BINARY。 下面,我们用代码来实现二值化阈值处理,具体代码如下所示: ``` import cv2 img = cv2.imread("4.jpg", 0) t, result_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.waitKey() cv2.destroyAllWindows() ``` 运行之后,效果如下所示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/100117pl2p2x7ygt8ggbnb.png) 可以看到通过二值化阈值处理,我们的图像有点像素描画的效果。 反二值化阈值处理的结果也是仅有两个值的二值图像,与二值化的区别在于,就是将其大于赋值255,小于赋值0颠倒过来。 修改代码,我们看看运行的效果: ``` import cv2 img = cv2.imread("4.jpg", 0) t, result_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.waitKey() cv2.destroyAllWindows() ``` ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/100152i4bevzt3rrf90jtp.png) 截断阈值化处理会将原图像中大于阈值的像素点的值设定为阈值,小于或等于像素点的值保持不变。也就是上面的将大于127像素的灰度图像全部更改为127,低于或等于127的保持不变。 ``` import cv2 img = cv2.imread("4.jpg", 0) t, result_img = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.waitKey() cv2.destroyAllWindows() ``` 运行之后,得到的效果如下所示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/100215ttx5fahhwriej3lj.png) 超阈值零处理会将图像中大于阈值的像素点的值处理为0,小于或等于阈值的像素点保持不变。也就是将大于127的处理为0,小于等于127的保持不变。 ``` import cv2 img = cv2.imread("4.jpg", 0) t, result_img = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.waitKey() cv2.destroyAllWindows() ``` 运行之后,效果如下: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/100235kzq6wtlogxm0kr7x.png) 低阈值零处理是将图像中小于或等于阈值的像素点处理为0,大于阈值的像素点保持不变。也就是小于等于127的全部赋值为0,大于127的保持不变。 ``` import cv2 img = cv2.imread("4.jpg", 0) t, result_img = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.waitKey() cv2.destroyAllWindows() ``` 运行之后,效果如下所示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202107/31/100253qhfi90eghezpfzkd.png) 文章摘自掘金
  • [其他] OpenCV(14)---几何变换之重映射
    什么是重映射把一副图像内的像素点放置到另一幅图像内的指定位置,这个过程我们称为重映射。简单点理解,也就是copy一个图像到另一个图像中。在OpenCV中,它给我们提供了cv2.remap()函数作为重映射,其定义如下:def remap(src, map1, map2, interpolation, dst=None, borderMode=None, borderValue=None)  src:代表原始图像map1:可以表示(x,y)点的一个映射,也可以表示CV_16SC2、CV_32FC1、CV_32FC2类型(x,y)点的x值map2:当前map1表示(x,y)点的一个映射时,该值为空。当map1表示CV_16SC2、CV_32FC1、CV_32FC2类型(x,y)点的x值时,该值时CV_16UC1、CV_32FC1类型(x,y)点的y值。interpolation,borderMode,borderValue与前文类似。需要注意,map1指代的是像素点所在位置的列号,map2指代的是像素点所在位置的行号。copy像素点现在我们假设有一个需求,将目标图像内的所有像素点都映射为原始图像内的第100行,200列上的像素点。具体实现如下:import cv2 import numpy as np img = cv2.imread("4.jpg") rows, cols, ch = img.shape mapx = np.ones(img.shape[:2], np.float32) * 200 mapy = np.ones(img.shape[:2], np.float32) * 100 result_img = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.waitKey() cv2.destroyAllWindows() 如上面代码所示,我们将所有像素点都设置为原始图像(100,200)点的像素点,会得到一个非常纯色的图像,效果如下所示: copy整个图像那么既然能copy某个像素点,那么也肯定能copy整个图像。下面,我们将上图左边的图像全部copy到右边,具体代码如下:import cv2 import numpy as np img = cv2.imread("4.jpg") rows, cols, ch = img.shape mapx = np.ones(img.shape[:2], np.float32) mapy = np.ones(img.shape[:2], np.float32) for i in range(rows): for j in range(cols): mapx.itemset((i,j),j)#设置每个点映射原图的Y坐标 mapy.itemset((i,j),i)#设置每个点映射原图的X坐标 result_img = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.waitKey() cv2.destroyAllWindows() 这里,我们将所有点映射到所有点,每个像素点一一对应,就完成了copy原图像。运行之后,效果如下所示: 绕X轴翻转通过cv2.remap()函数,我们不仅可以重映射像素点,还可以翻转过来映射,也就是通过它实现X轴的翻转效果,只要保证X轴不变,并且Y坐标值以X轴为对称进行交换即可。修改上面代码中的某一行,代码如下:import cv2 import numpy as np img = cv2.imread("4.jpg") rows, cols, ch = img.shape mapx = np.ones(img.shape[:2], np.float32) mapy = np.ones(img.shape[:2], np.float32) for i in range(rows): for j in range(cols): mapx.itemset((i,j),j) mapy.itemset((i,j),rows-1-i)#修改这一行即可,对称 result_img = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.waitKey() cv2.destroyAllWindows() 运行之后,我们得到了绕X轴翻转的图像,效果如下: 绕Y轴翻转既然我们可以通过cv2.remap()函数绕X翻转,那么肯定的也可以绕Y轴翻转,只要将X坐标值以Y轴为对称进行交换即可。话不多说,直接上代码:import cv2 import numpy as np img = cv2.imread("4.jpg") rows, cols, ch = img.shape mapx = np.ones(img.shape[:2], np.float32) mapy = np.ones(img.shape[:2], np.float32) for i in range(rows): for j in range(cols): mapx.itemset((i,j),cols-1-j)#修改这一行即可 mapy.itemset((i,j),i)# result_img = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.waitKey() cv2.destroyAllWindows() 运行之后,我们得到绕Y轴翻转的图像: 绕XY轴翻转那么绕XY轴一起翻转呢?这里二行代码一起更改:import cv2 import numpy as np img = cv2.imread("4.jpg") rows, cols, ch = img.shape mapx = np.ones(img.shape[:2], np.float32) mapy = np.ones(img.shape[:2], np.float32) for i in range(rows): for j in range(cols): mapx.itemset((i,j),cols-1-j) mapy.itemset((i,j),rows-1-i) result_img = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,效果如下所示: 压缩一半也就是将原图缩小一般,按将Y轴缩小一般,只需要将X轴设置为2倍即可。具体代码如下所示:import cv2 import numpy as np img = cv2.imread("4.jpg") rows, cols, ch = img.shape mapx = np.ones(img.shape[:2], np.float32) mapy = np.ones(img.shape[:2], np.float32) for i in range(rows): for j in range(cols): mapx.itemset((i,j),j) mapy.itemset((i,j),2*i)#修改这行代码即可 result_img = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR) cv2.imshow("img", img) cv2.imshow("result_img", result_img) cv2.waitKey() cv2.destroyAllWindows() 运行之后,效果显示如下: 来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • [其他] OpenCV(13)---几何变换之透视
    更复杂的仿射变化上篇博文讲解了2种最基本的仿射变换:平移与旋转。但OpenCV还给我们提供了函数cv2.getAffineTransform()来生成仿射函数cv2.warpAffine()所使用的转换矩阵M。该函数的定义如下:def getAffineTransform(src, dst):  src:代表输入图像的三个点坐标dst:代表输出图像的三个点坐标用过PS的都知道,我们在PS中使用快捷键Ctrl+T就可以随意拉扯图像,因为图像上下左右各有四个点负责定位。而src中的3个坐标与PS完全一致,分别为左上角,右上角,左下角,只是少了一个点,但定位绰绰有余,当然映射后只能是平行四边形,因为缺失一个坐标。下面,我们来随意定位图像,并实现拉扯效果:import cv2 import numpy as np img = cv2.imread("4.jpg") rows,cols,ch = img.shape p1=np.float32([[0,0],[cols-1,0],[0,rows-1]]) p2=np.float32([[0,rows*0.33],[cols*0.85,rows*0.25],[cols*0.15,rows*0.7]]) M = cv2.getAffineTransform(p1,p2) move_img = cv2.warpAffine(img, M, (cols, rows)) cv2.imshow("img", img) cv2.imshow("move_img", move_img) cv2.waitKey() cv2.destroyAllWindows() 运行之后,效果如下图所示: p1是原始图像的坐标,这里稍微有点变换。原图坐标就是左上角[0,0],右上角[cols,0],左下角[0,rows]。变换后的坐标左上角[0,rows0.33],右上角[cols0.85,rows0.25],左下角[cols0.15,rows*0.7]。透视之所以要讲解上面的平行四边形仿射,是因为我们的透视是任意四边形,通过上面的代码我们更容易理解透视的概念。在OpenCV中,透视变换通过函数cv2.warpPerspective()实现,该函数的定义如下:def warpPerspective(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None): src:要透视的原始图像M:变换透视图像的3*3变换矩阵dsize:代表输出图像的尺寸大小flags,borderMode,borderValue三个参数与前文类似这里不多做介绍。下面,我们将上面的图像变换成任意四边形,具体代码如下所示:import cv2 import numpy as np img = cv2.imread("4.jpg") rows, cols, ch = img.shape p1 = np.float32([[0, 0], [cols, 0], [0, rows], [cols, rows]]) p2 = np.float32([[0, rows * 0.33], [cols * 0.85, rows * 0.25], [cols * 0.15, rows * 0.7], [cols * 0.85, rows * 0.85]]) M = cv2.getPerspectiveTransform(p1, p2) move_img = cv2.warpPerspective(img, M, (cols, rows)) cv2.imshow("img", img) cv2.imshow("move_img", move_img) cv2.waitKey() cv2.destroyAllWindows() 透视函数warpPerspective通过getPerspectiveTransform获得变换矩阵。运行之后,效果如下所示:来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • [其他] OpenCV(12)---几何变换之仿射
    仿射在OpenCV中,仿射变换是指图像经过一系列的几何变换来实现的平移,旋转等多种操作。该变换能够保持图像的平直性与平行性。平直性是指图像经过仿射变换后,直线仍然是直线;平行性是指图像在完成仿射变换后,平行性依然是平行线。在OpenCV中,它给我们提供的仿射函数为cv2.warpAffine(),其通过一个变换矩阵M实现,对于矩阵运算不大了解的,可以记住后面讲解的,也可以学习离散数学或线性代数,两者都讲解到了矩阵运算。仿射函数的定义如下:def warpAffine(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None) src:代表要仿射的原始图像M:代表一个2*3的变换矩阵,使用不同的变换矩阵,就可以实现不同的仿射变换。dszie:代表输出图像的尺寸大小。dst:代表仿射后的输出图像flags:代表插值方法,默认为INTER_LINEAR。当该值为WARP_INVERSE_MAP时,意味着M是逆变换类型,实现从目标图像dst到原始图像src的逆变换。详细参数,上篇博文表格就是。borderMode:代表边类型,默认为BORDER_CONSTANT。当该值为BORDER_TRANSPARENT时,意味着目标图像内的值不做改变,这些值对应原始图像内的异常值。borderValue:代表边界值,默认是0。综上所示,我们常用的参数为:src,M,dsize。平移已知仿射公式为: 假设我们现在要将图像向右平移50个像素,向下平移100个像素,那么公式替换后如下所示:dst(x,y)=src(x+50,y+100)dst(x,y)=src(1x,+0y+50,0x+1y+100)得到M中的各个元素值为:M11=1M12=0M13=50M21=0M22=1M23=100综上所述,右平移50个像素,向下平移100个像素的变换矩阵为:已知变换矩阵与原始图像,那么很简单的我们就可以完成图像的平移操作,具体代码如下所示:import cv2 import numpy as np img = cv2.imread("4.jpg") h, w = img.shape[:2] x = 50 y = 100 M = np.float32([[1, 0, x], [0, 1, y]]) move_img = cv2.warpAffine(img, M, (w, h)) cv2.imshow("img", img) cv2.imshow("move_img", move_img) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,效果如下所示: 旋转在使用函数cv2.warpAffine()对图像进行旋转时,可以通过函数cv2.getRotationMatrix2D()获取转换矩阵。该函数的语法格式为:def getRotationMatrix2D(center, angle, scale):center:为旋转的中心angle:为旋转的角度,正数表示逆时针旋转,负数表示顺时针旋转scale:为变换尺寸(也就是前文说的缩放大小)下面,我们来将上图在旋转45度,具体代码如下所示:import cv2 img = cv2.imread("4.jpg") h, w = img.shape[:2] M = cv2.getRotationMatrix2D((w / 2, h / 2), 45, 0.6) move_img = cv2.warpAffine(img, M, (w, h)) cv2.imshow("img", img) cv2.imshow("move_img", move_img) cv2.waitKey() cv2.destroyAllWindows() 更改M变换矩阵就行,这里(w / 2, h / 2)为图像的中心坐标,45为正数,就是逆时针旋转45度,0.6就是将图像缩放0.6倍。运行之后,效果如下所示:来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • [其他] OpenCV(11)---几何变换之翻转
    前言经过前文的介绍,我们已经掌握了基础的缩放功能。本篇博文将带领大家一起学习OpenCV中另一个几何变换,也就是翻转。翻转在OpenCV中,它给我们提供cv2.flip()函数来实现翻转,该函数即可以实现水平方向翻转,也可以实现垂直方向翻转,当然也可以两个方向同时翻转,它的定义如下:def flip(src, flipCode, dst=None): 复制代码src:原始图像dst=代表和原始图像具有同样大小,类型的目标图像。flipCode:代表旋转类型旋转类型有3个,如下表所示:参数值说明含义0只能是0X轴翻转正数可以是任意正数绕Y轴翻转负数可以是任意负数绕XY轴翻转实现翻转既然我们已经了解了函数的具体定义,已经每个参数的作用,下面我们通过一个例子来实现所有的翻转效果。具体代码如下所示:import cv2 img = cv2.imread("4.jpg") img_x = cv2.flip(img, 0) img_y = cv2.flip(img, 1) img_xy = cv2.flip(img, -1) cv2.imshow("img", img) cv2.imshow("x", img_x) cv2.imshow("y", img_y) cv2.imshow("xy", img_xy) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,效果如下所示:作者:极客学编程链接:https://juejin.cn/post/6984377284627005471来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • [其他] OpenCV(10)---几何变换之缩放
    前言对于色彩空间的转换,我们就通过HSV实战给大家讲解介绍。至于其他的色彩空间的转换与应用,后续学到其他知识在单独来将。从本篇开始,我们将学习OpenCV的几何变换。比如,在图像处理的领域,我们会经常碰到图像的缩放,旋转等操作,特别是对于有过使用PS经验的用户,对于这些操作肯定手到擒来,但其实底层的代码就是后面要讲解的知识。本篇我们将介绍缩放的知识。缩放在OpenCV中,它给我们提供的缩放函数为cv2.resize(),该函数的定义如下:def resize(src, dsize, dst=None, fx=None, fy=None, interpolation=None): 复制代码src:代表需要缩放的原始图像dsize:缩放图像的大小,也可以叫尺寸fx:代表水平方向缩放的比例fy:代表垂直方向缩放的比例interpolation:代表插值方式interpolation插值的值如表:类型说明cv2.INTER_LINEAR双线性插值(默认方式)cv2.INTER_NEARSET最临近插值cv2.INTER_CUBIC三次样条插值。首先对源图像附近的4*4近邻区域进行三次样条拟合,然后将目标像素对应的三次样条值作为目标图像对应的像素点的值cv2.INTER_AREA区域插值,根据当前像素点周边区域的像素实现当前像素点的采样,该方法类似最临近插值方式cv2.INTER_LANCZ0S4一种使用8*8近邻的Lanczos插值方式cv2.INTER_LINEAR_EXACT位精确双线性插值cv2.INTER_MAX差值编码掩码cv2.WARP_FILL_OUTLIERS标值,填补目标图像中所有的像素。如果它们中的一些对应源图像中的奇异点(离群值),则它们将设置为0cv2.WARP_INVERSE_MAP标值,逆变换,例如,极坐标变换。如果flag未被设置,则进行转换:dst(Ø,p)=src(x,y);如果flag被设置,则进行转换:dst(x,y)=src(Ø,p)在cv2.resize()函数中,目标图像的大小可以通过“dsize”或者“fx,fy”二者其中一个来指定。当你使用dsize来指定时,则无论是否指定fx,fy,都有参数dsize来决定目标图像的大小,也就是dsize优先级最高。具体数学公式如下:宽度=(double)dszie.width/src.cols高度=(double)dszie.height/src.rows需要注意的dsize的第1个参数是列数,第2个参数才是行数,与shape相反。而当你使用fx,fy指定目标图像大小时,数学公式如下:dsize=Size(round(fxsrc.cols),round(fysrc.rows))至于最后的参数插值,是指对图像进行几何处理时,给无法直接通过映射得到值的像素点赋值。例如,将在特定大小的区域将图像放大2倍,有的图像可能小了不够放缺失一些像素点,有的图像可能大了多出去一些像素点,对于这些像素点,插值决定了如何确定它们的值。dsize实现缩放既然了解了OpenCV中缩放函数resize的所有参数,下面我们来实现一个简单的图像缩放,具体代码如下所示:import cv2 img = cv2.imread("4.jpg") rows,cols=img.shape[:2] size=(int(cols*2),int(rows*1)) result = cv2.resize(img, size) cv2.imshow("img", img) print(img.shape) cv2.imshow("result", result) print(result.shape) cv2.waitKey() cv2.destroyAllWindows() 复制代码这里,我们首先通过img.shape获取图片的长宽像素,然后将行不变,列放大2倍。运行之后,我们会得到下图。fx,fy实现缩放上面我们是通过dszie参数实现的缩放,现在我们通过fx,fy参数实现缩放,具体代码如下所示:import cv2 img = cv2.imread("4.jpg") result = cv2.resize(img, None,fx=2,fy=1) cv2.imshow("img", img) print(img.shape) cv2.imshow("result", result) print(result.shape) cv2.waitKey() cv2.destroyAllWindows() 复制代码这段代码实现的效果与上面一致,运行结果就不展示了。可以看出来,fx与fy的代码会更加的简洁明了。作者:极客学编程链接:https://juejin.cn/post/6984003927372988423来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • [其他] OpenCV(9)---HSV标记肤色与实现艺术效果
    标记肤色前面,我们通过标记H通道上的红色,从而提取图片上的红色有效区域。那么同样的,我们可以限定肤色的范围,提取人脸的,以达到抠图的效果。首先,肤色不仅要关注H通道,同样也需要关注S通道。所以,我们首先需要介绍一个函数:split(),定义如下:img = cv2.imread("4.jpg") hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(hsv) 复制代码如上面代码所示,我们可以通过cv2.splite()函数,来获取HSV图像上,所有通道的值。首先,我们假定人的肤色的大致范围,其色调在[5,170]之间,饱和度在[25,166]之间。这样,我们可以按上一节的内容来获取限定范围内的图像,代码如下所示:import cv2 img = cv2.imread("4.jpg") hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(hsv) hmask=cv2.inRange(h,5,170) smask=cv2.inRange(s,25,166) mask=hmask & smask result=cv2.bitwise_and(img,img,mask=mask) cv2.imshow("img",img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,只要与皮肤颜色相近的图像,都会被提取出来,而其他部分都会变成黑色。实现艺术效果前面,我们介绍了各种HS通道的应用,一直没有单独的或者强调V的重要性。这里,我们来通过V通道的值,实现一些有趣的艺术效果。import cv2 img = cv2.imread("9.jpg") hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(hsv) v[:,:]=255 newHSV=cv2.merge([h,s,v]) result=cv2.cvtColor(newHSV,cv2.COLOR_HSV2BGR) cv2.imshow("img",img) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,有点非主流的感觉。作者:极客学编程链接:https://juejin.cn/post/6984003832183259173来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • [其他] OpenCV(8)---色彩空间转换与HSV的简单应用(2)
    RGB与HSV互相转换同上面代码类似,通过cv2.cvtColor()来转换色彩空间:import cv2 img = cv2.imread("4.jpg", -1) hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) cv2.imshow("bgr", img) cv2.imshow("hsv", hsv) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,我们会得到如下所示的图像:在前面的理论知识中,我们可以根据色调的值获取某种颜色,也就是可以通过在HSV的H通道上的值,提取特定的颜色。这种提取分析颜色的优势可以在人脸识别中识别肤色等。下面,我们来用蓝色试一试,具体代码如下所示:import cv2 img = cv2.imread("4.jpg", -1) img[:, :, 0] = 255 Blue = img blueHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) cv2.imshow("imgBlue", Blue) cv2.imshow("blueHSV", blueHSV) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,效果如下所示:现在,我们来提取它的红色区域,完整代码如下所示:import cv2 import numpy as np img = cv2.imread("4.jpg") hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) cv2.imshow("hsv", hsv) minBlue = np.array([0, 50, 50]) maxBlue = np.array([30, 255, 255]) # 确定蓝色区域 mask = cv2.inRange(hsv, minBlue, maxBlue) # 通过按位与获取蓝色区域 blue_img = cv2.bitwise_and(img, img, mask=mask) cv2.imshow("blue", blue_img) cv2.waitKey() cv2.destroyAllWindows() 复制代码这里,我们用到了一个新的方法cv2.inRange(),它的定义如下所示:def inRange(src, lowerb, upperb, dst=None): 复制代码src:表示要检查的数组或头像lowerb:表示范围下界upperb:表示范围上界通过该方法,我们能判断图像内的像素点的像素值是否在某个区间。经过前面的理论讲解,我们知道,HSV的H等于0为红色。为了兼容性,我们需要将红色的值上下扩展一些,但本身是颜色范围不能小于0,所以我们只能扩展上界限,也就是扩大30范围。而HSV中的S通道,V通道取值范围为[100,255]。所以,这里我们为了获取到图像红色的值,将界限限定在[0, 50, 50]到[30, 255, 255]之间,运行之后,提取到了图像的红色: 从本例可以看出来,我们通过cv2.inRange()可以将图像内指定范围的值标注出来,在返回到mask中。如果图像的值位于该区间,则mask对应位置上的值为255,反之为0。然后,通过掩摸按位与运算将指定颜色取出来。这里我们的bitwise_and有了第3个参数mask,利用掩摸(mask)进行“与”操作,即掩膜图像白色区域是对需要处理图像像素的保留,黑色区域是对需要处理图像像素的剔除。作者:极客学编程链接:https://juejin.cn/post/6983687401596157988来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • [其他] OpenCV(8)---色彩空间转换与HSV的简单应用
    前言经过前面的理论知识介绍,我们已经掌握了各种色彩空间类型。本篇博文主要介绍在OpenCV中,如何用代码实现色彩空间类型的转换。RGB与GRAY互相转换在OpenCV内,我们使用cv2.cvtColor()函数实现色彩空间的转换。该函数色彩空间类型用枚举类型表示,其中COLOR_BGR2GRAY枚举类型就是专门提供给给RGB转GRAY的。具体代码如下所示:import cv2 img = cv2.imread("4.jpg", -1) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) cv2.imshow("rgb", img) cv2.imshow("gray", gray) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,效果如下图所示:接着,我们再来看看GRAY如何转换为RGB,具体代码如下所示:import cv2 img = cv2.imread("4.jpg", 0) bgr = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) cv2.imshow("gray", img) cv2.imshow("rgb", bgr) cv2.waitKey() cv2.destroyAllWindows() 复制代码运行之后,显示效果如下图所示: 需要注意的是,RGB图像的颜色3维矩阵是BGR,所以我们转换的时候都是通过BGR2GRAY与GRAY2BGR进行的。从这里我们也可以看出来,其枚举类型常量其实非常好理解,无非就是将转换类型名称颠倒即可。但是,我们发现灰度图像转换为RGB图像是没有变化的,因为灰度图像没有颜色值,运算不可能通过凭空想象的方式还原,但是灰度图像转RGB图像还有具有一定意义的,假如你需要上色,那么灰度图像是一个二维矩阵,它不是颜色值的三维矩阵,你是无法赋值的。而通过GRAY转RGB后,可以像操作RGB图像一样更改某个像素的颜色值,虽然它是灰色。作者:极客学编程链接:https://juejin.cn/post/6983687401596157988来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
总条数:322 到第
上滑加载中