OpenCV-Python入门笔记

简介: OpenCV-Python入门笔记

首先导入所需要的库文件

import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# 设置随机种子,确保结果可复现
SEED = 42
def Set_Seed():
    np.random.seed(42)
    cv2.setRNGSeed(42)
Set_Seed()
def Img_Show(img):
    cv2.imshow("img", img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
img = cv2.imread("./photo/img.png")
Img_Show(img)

image.png


1. 图像元素访问,通道分离与合并


# 功能: 对图片随机添加噪声白点
# 参数:img:传入图像
#        n:设置噪声白点的数量
def salt(img, n):
    for k in range(n):
        i = int(np.random.random() * img.shape[1])
        j = int(np.random.random() * img.shape[0])
        if img.ndim == 2: 
            img[j,i] = 255
        elif img.ndim == 3: 
            img[j,i,0]= 255
            img[j,i,1]= 255
            img[j,i,2]= 255
    return img
def Channel_Split_and_Merge():
    img = cv2.imread("./photo/img.png")   # shape:(495, 700, 3)
    print("img[200,100]:", img[200,100])
    # 通道分离
    b, g, r = cv2.split(img)  
    cv2.imshow("Blue", r)
    cv2.imshow("Red", g)
    cv2.imshow("Green", b)
    # 随机增加噪声白点
    saltImage = salt(img, 1000)
    cv2.imshow("saltImage",saltImage)
    # 通道合并
    merged = cv2.merge([b,g,r])
    cv2.imshow("merged", merged)
    print(merged.strides)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
Channel_Split_and_Merge()
img[200,100]: [84 16 49]
(2100, 3, 1)


2. 直方图的计算与显示


help(cv2.calcHist)
Help on built-in function calcHist:
calcHist(...)
    calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) -> hist
    .   @overload
cv2.calcHist([image], [0], None, [256], [0.0,255.0])


其中:


第一个参数必须用方括号括起来。


第二个参数是用于计算直方图的通道, [0]这里使用灰度图计算直方图,所以就直接使用第一个通道。


第三个参数是Mask,这里没有使用,所以用None。


第四个参数是histSize,表示这个直方图分成多少份(即多少个直方柱)。


第五个参数是表示直方图中各个像素的值,[0.0, 256.0]表示直方图能表示像素值从0.0到256的像素。最后是两个可选参数,由于直方图作为函数结果返回了,所以第六个hist就没有意义了(待确定),最后一个accumulate是一个布尔值,用来表示直方图是否叠加。


  • 利用opencv来绘制直方图
def calcAndDrawHist(image, color):  
    hist= cv2.calcHist([image], [0], None, [256], [0.0,255.0])  
    minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist)  
    histImg = np.zeros([256,256,3], np.uint8)  
    hpt = int(0.9* 256);  
    for h in range(256):  
        intensity = int(hist[h]*hpt/maxVal)  
        cv2.line(histImg,(h,256), (h,256-intensity), color)  
    return histImg
def Hist_Show_and_Cal():
    img = cv2.imread("./photo/img.png")
    b, g, r = cv2.split(img)  
    histImgB = calcAndDrawHist(b, [255, 0, 0]) 
    cv2.imshow("histImgB", histImgB)
    cv2.waitKey(0)  
    cv2.destroyAllWindows()
Hist_Show_and_Cal()

image.png


  • 利用matplotlib来绘制直方图
img = cv2.imread("./photo/img.png")
HistSize = 256
histImgB = cv2.calcHist([img], [0], None, [HistSize], [0.0,255.0])  
histImgG = cv2.calcHist([img], [1], None, [HistSize], [0.0,255.0])  
histImgR = cv2.calcHist([img], [2], None, [HistSize], [0.0,255.0]) 
def Get_HistMax(hist):
    minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist)
    return maxVal
HistMax = max(Get_HistMax(histImgB), Get_HistMax(histImgG), Get_HistMax(histImgR))
x = np.arange(0, HistSize)
plt.figure(figsize=[10, 5])
# data = np.column_stack((x, histImgB.flatten()/HistMax*HistSize))
# pos.shape: (256, 2)
plt.plot(x, histImgB.flatten()/HistMax*HistSize, color='blue')
plt.plot(x, histImgG.flatten()/HistMax*HistSize, color='green')
plt.plot(x, histImgR.flatten()/HistMax*HistSize, color='red')
[<matplotlib.lines.Line2D at 0x190de575b88>]

image.png


3. 形态学处理


element_cv = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
element_np = np.uint8(np.zeros((5,5)))
for i in range(len(element_np)):
    element_np[i][2] = 1
    element_np[2][i] = 1
np.equal(element_cv, element_np)
array([[ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True]])


  • 腐蚀和膨胀
#原图像
cv2.imshow("Origin", img)
#OpenCV定义的结构元素
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
# kernel:
# array([[1, 1, 1],
#        [1, 1, 1],
#        [1, 1, 1]], dtype=uint8)
#腐蚀图像
eroded = cv2.erode(img,kernel)
#显示腐蚀后的图像
cv2.imshow("Eroded Image",eroded)
#膨胀图像
dilated = cv2.dilate(img,kernel)
#显示膨胀后的图像
cv2.imshow("Dilated Image",dilated)
#NumPy定义的结构元素
NpKernel = np.uint8(np.ones((3,3)))
Nperoded = cv2.erode(img,NpKernel)
#显示腐蚀后的图像
cv2.imshow("Eroded by NumPy kernel",Nperoded);    
cv2.waitKey(0)
cv2.destroyAllWindows()
# 说明两个方式处理是完全一样的
np.allclose(eroded, Nperoded)
np.alltrue(eroded==Nperoded)
True


腐蚀和膨胀的处理很简单,只需设置好结构元素,然后分别调用cv2.erode(…)和cv2.dilate(…)函数即可,其中第一个参数是需要处理的图像,第二个是结构元素。返回处理好的图像。


  • 开运算和闭运算
#定义结构元素
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5))
# kernel:
# array([[1, 1, 1, 1, 1],
#        [1, 1, 1, 1, 1],
#        [1, 1, 1, 1, 1],
#        [1, 1, 1, 1, 1],
#        [1, 1, 1, 1, 1]], dtype=uint8)
#闭运算
closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
#显示腐蚀后的图像
cv2.imshow("Close",closed);
#开运算
opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
#显示腐蚀后的图像
cv2.imshow("Open", opened);
cv2.waitKey(0)
cv2.destroyAllWindows()


闭运算用来连接被误分为许多小块的对象,而开运算用于移除由图像噪音形成的斑点。因此,某些情况下可以连续运用这两种运算。如对一副二值图连续使用闭运算和开运算,将获得图像中的主要对象。同样,如果想消除图像中的噪声(即图像中的“小点”),也可以对图像先用开运算后用闭运算,不过这样也会消除一些破碎的对象。


  • 用形态学运算进行边缘检测

形态学检测边缘的原理很简单,在膨胀时,图像中的物体会想周围“扩张”;腐蚀时,图像中的物体会“收缩”。比较这两幅图像,由于其变化的区域只发生在边缘。所以这时将两幅图像相减,得到的就是图像中物体的边缘。不过效果并不是太好,实际使用时请用Canny或Harris等算法。

image = cv2.imread("./photo/building.jfif",0);
#构造一个3×3的结构元素 
element = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
dilate = cv2.dilate(image, element)
erode = cv2.erode(image, element)
# cv2.imshow("dilate",dilate)
# cv2.imshow("erode",erode)
#将两幅图像相减获得边,第一个参数是膨胀后的图像,第二个参数是腐蚀后的图像
result_sub = cv2.absdiff(dilate,erode)
# cv2.imshow("result_first",result)
# 查看灰度阈值,发现大部分处于40-50以下
hist =  cv2.calcHist([result_sub], [0], None, [HistSize], [0.0,255.0])
plt.plot(x, hist.flatten())
#上面得到的结果是灰度图,将其二值化以便更清楚的观察结果
# 50表示: 灰度值小于40置0, 大于40值最大值255
retval, result = cv2.threshold(result_sub, 50, 255, cv2.THRESH_BINARY)
#反色,即对二值图每个像素取反
result = cv2.bitwise_not(result)
# 或者直接使用取反色算子cv2.THRESH_BINARY_INV,则无需使用cv2.bitwise_not(result)
# retval, result = cv2.threshold(result, 50, 255, cv2.THRESH_BINARY_INV)
#显示图像
cv2.imshow("result",result); 
cv2.waitKey(0)
cv2.destroyAllWindows()

image.png


处理结果,可以提取边缘信息

image.png


  • 拐点检测

与边缘检测不同,拐角的检测的过程稍稍有些复杂。但原理相同,所不同的是先用十字形的结构元素膨胀像素,这种情况下只会在边缘处“扩张”,角点不发生变化。接着用菱形的结构元素腐蚀原图像,导致只有在拐角处才会“收缩”,而直线边缘都未发生变化。(我的理解是这里由于边缘线经历了先膨胀再收缩所以不变,而角点只是收缩所有会有变化)


第二步是用X形膨胀原图像,角点膨胀的比边要多。这样第二次用方块腐蚀时,角点恢复原状,而边要腐蚀的更多。所以当两幅图像相减时,只保留了拐角处。


image = cv2.imread("./photo/building.jfif", 0);
# 构造5×5的结构元素,分别为十字形、菱形、方形和X型
cross = cv2.getStructuringElement(cv2.MORPH_CROSS,(5, 5))
# 构造菱形结构元素
diamond = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5))
diamond[0, 0] = 0
diamond[0, 1] = 0
diamond[1, 0] = 0
diamond[4, 4] = 0
diamond[4, 3] = 0
diamond[3, 4] = 0
diamond[4, 0] = 0
diamond[4, 1] = 0
diamond[3, 0] = 0
diamond[0, 3] = 0
diamond[0, 4] = 0
diamond[1, 4] = 0
# 构造方形和十字形结构
square = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5))
x = cv2.getStructuringElement(cv2.MORPH_CROSS,(5, 5))
#使用cross膨胀图像
result1 = cv2.dilate(image,cross)
#使用菱形腐蚀图像
result1 = cv2.erode(result1, diamond)
cv2.imshow("result1", result1)
#使用X膨胀原图像 
result2 = cv2.dilate(image, x)
#使用方形腐蚀图像 
result2 = cv2.erode(result2,square)
cv2.imshow("result2", result2)
#将两幅闭运算的图像相减获得角 
result = cv2.absdiff(result2, result1)
#使用阈值获得二值图
retval, result = cv2.threshold(result, 40, 255, cv2.THRESH_BINARY)
#在原图上用半径为5的圆圈将点标出。
# for j in range(result.size):
#     y = j / result.shape[0] 
#     x = j % result.shape[0] 
#     if result[x, y] == 255:
#         cv2.circle(image, (y, x), 5, (255,0,0))
cv2.imshow("Result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()


结果展示:

image.png

image.png


4. 初级滤波内容


当我们观察一张图片时,我们观察的是图像中有多少灰度级(或颜色)及其分布。根据灰度分布的不同来区分不同的图像。但还有其他方面可以对图像进行分析。我们可以观察图像中灰度的变化。某些图像中包含大量的强度不变的区域(如蓝天),而在其他图像中的灰度变化可能会非常快(如包含许多小物体的拥挤的图像)。因此,观察图像中这些变化的频率就构成了另一条分类图像的方法。这个观点称为频域。而通过观察图像灰度分布来分类图像称为空间域。


频域分析将图像分成从低频到高频的不同部分。低频对应图像强度变化小的区域,而高频是图像强度变化非常大的区域。目前已存在若干转换方法,如傅立叶变换或余弦变换,可以用来清晰的显示图像的频率内容。注意,由于图像是一个二维实体,所以其由水平频率(水平方向的变化)和竖直频率(竖直方向的变化)共同组成。


在频率分析领域的框架中,滤波器是一个用来增强图像中某个波段或频率并阻塞(或降低)其他频率波段的操作。低通滤波器是消除图像中高频部分,但保留低频部分。高通滤波器消除低频部分。


  • 用低通滤波来平滑图像

低通滤波器的目标是降低图像的变化率。如将每个像素替换为该像素周围像素的均值。这样就可以平滑并替代那些强度变化明显的区域。


高斯模糊可以在某些情况下,需要对一个像素的周围的像素给予更多的重视。因此,可通过分配权重来重新计算这些周围点的值。这可通过高斯函数(钟形函数,即喇叭形数)的权重方案来解决。


img = cv2.imread("./photo/img.png")
cv2.imshow("Origin", img)
# 普通的模糊方式
# 以下两种方式是等价的,其中(5,5)是控制模糊程度
result = cv2.blur(img, (5,5))
result1 = cv2.boxFilter(img, -1, (5,5))
cv2.imshow("Blur", result)
cv2.imshow("BoxFilter", result1)
# 高斯模糊
gaussianResult = cv2.GaussianBlur(img,(5,5),1.5)
cv2.imshow("gaussianResult", gaussianResult)
cv2.waitKey(0)
cv2.destroyAllWindows()


低通滤波与高斯滤波的不同之处在于:低通滤波中,滤波器中每个像素的权重是相同的,即滤波器是线性的。而高斯滤波器中像素的权重与其距中心像素的距离成比例。


  • 使用中值滤波消除噪点
# 功能: 对图片随机添加噪声白点
# 参数:img:传入图像
#        n:设置噪声白点的数量
def salt(img, n):
    for k in range(n):
        i = int(np.random.random() * img.shape[1])
        j = int(np.random.random() * img.shape[0])
        if img.ndim == 2: 
            img[j,i] = 255
        elif img.ndim == 3: 
            img[j,i,0]= 255
            img[j,i,1]= 255
            img[j,i,2]= 255
    return img
img = cv2.imread("./photo/img.png")
cv2.imshow("img", img)
# 产生带噪声的图片
saltimg = salt(img, 1000)
cv2.imshow("saltimg", saltimg)
# 过滤噪声
medianimg = cv2.medianBlur(saltimg, 3)
cv2.imshow("medianimg", medianimg)
cv2.waitKey(0)
cv2.destroyAllWindows()

image.png


由于中值滤波不会处理最大和最小值,所以就不会受到噪声的影响。相反,如果直接采用blur进行均值滤波,则不会区分这些噪声点,滤波后的图像会受到噪声的影响。

中值滤波器在处理边缘也有优势。但中值滤波器会清除掉某些区域的纹理(如背景中的树)。

blurimg = cv2.blur(saltimg, (5,5))
cv2.imshow("blurimg", blurimg)
cv2.waitKey(0)
cv2.destroyAllWindows()

image.png

使用blur来消除噪声的效果虽然也消除的噪声,但是比较模糊


5. Sobel算子


dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])

Sobel算子依然是一种过滤器,只是其是带有方向的:函数返回其处理结果。前四个是必须的参数:


第一个参数是需要处理的图像;第二个参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。


其后是可选的参数:dst是选择图片的位数。ksize是Sobel算子的大小,必须为1、3、5、7。scale是缩放导数的比例常数,默认情况下没有伸缩系数;delta是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中;borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。


img = cv2.imread("./photo/img.png", 0)
# 使用16位有符号的数据类型,即cv2.CV_16S;先后对x方向与y方向进行求导
x = cv2.Sobel(img,cv2.CV_16S,1,0)
y = cv2.Sobel(img,cv2.CV_16S,0,1)
# 用convertScaleAbs()函数将其转回原来的uint8形式。否则将无法显示图像,而只是一副灰色的窗口
absX = cv2.convertScaleAbs(x)   
absY = cv2.convertScaleAbs(y)
cv2.imshow("absX", absX)
cv2.imshow("absY", absY)
# 由于Sobel算子是在两个方向计算的,还需组合起来
# dst = gamma+(src1*aplha + src2*beta) -> absX*0.5 + absY*0.5
dst = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
cv2.imshow("Result", dst)
cv2.waitKey(0)
cv2.destroyAllWindows()


结果如下所示:

image.png

image.png


6. Laplacian算子


图像中的边缘区域,像素值会发生“跳跃”,对这些像素求导,在其一阶导数在边缘位置为极值,这就是Sobel算子使用的原理——极值处就是边缘。

image.png


如果对像素值求二阶导数,会发现边缘处的导数值为0。

image.png

Laplace函数实现的方法是先用Sobel 算子计算二阶x和y导数,再求和,公式如下:

image.png

Laplacian(...)
    Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]]) -> dst


前两个是必须的参数:第一个参数是需要处理的图像;第二个参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;


其后是可选的参数:

dst不用解释了;

ksize是算子的大小,必须为1、3、5、7。默认为1。

scale是缩放导数的比例常数,默认情况下没有伸缩系数;delta是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中;

borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。


  • 不滤波提取边缘信息
img = cv2.imread("./photo/img.png", 0)
# 为了让结果更清晰,这里的ksize设为3
gray_lap = cv2.Laplacian(img,cv2.CV_16S,ksize = 3)
# 用convertScaleAbs()函数将其转回原来的uint8形式。否则将无法显示图像,而只是一副灰色的窗口
dst = cv2.convertScaleAbs(gray_lap)
# cv2.imshow('gray_lap',gray_lap)
cv2.imshow('laplacian',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

image.png

可以发现图片还是有比较多的噪声,现在通过低通去噪模糊再进行边缘处理,看看效果


  • 滤波提取边缘信息
img = cv2.imread("./photo/img.png", 0)
# 滤波处理
blurimg = cv2.blur(img,(3,3))
gaussianimg = cv2.GaussianBlur(img, (3,3), 1.5)
cv2.imshow('blurimg',blurimg)
cv2.imshow('gaussianimg',gaussianimg)
# 为了让结果更清晰,这里的ksize设为3
gray_lap = cv2.Laplacian(img,cv2.CV_16S,ksize = 3)
gray_lap_blur = cv2.Laplacian(blurimg,cv2.CV_16S,ksize = 3)
gray_lap_gaussian = cv2.Laplacian(gaussianimg,cv2.CV_16S,ksize = 3)
# 用convertScaleAbs()函数将其转回原来的uint8形式。否则将无法显示图像,而只是一副灰色的窗口
gray_lap = cv2.convertScaleAbs(gray_lap)
gray_lap_blur = cv2.convertScaleAbs(gray_lap_blur)
gray_lap_gaussian = cv2.convertScaleAbs(gray_lap_gaussian)
cv2.imshow('gray_lap',gray_lap)
cv2.imshow('gray_lap_blur',gray_lap_blur)
cv2.imshow('gray_lap_gaussian',gray_lap_gaussian)
cv2.waitKey(0)
cv2.destroyAllWindows()

image.png


7.Canny边缘检测


OpenCV-Python中Canny函数的原型为:


edge = cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])


必要参数:第一个参数是需要处理的原图像,该图像必须为单通道的灰度图;第二个参数是阈值1;第三个参数是阈值2。


其中较大的阈值2用于检测图像中明显的边缘,但一般情况下检测的效果不会那么完美,边缘检测出来是断断续续的。所以这时候用较小的第一个阈值用于将这些间断的边缘连接起来。

可选参数中apertureSize就是Sobel算子的大小。而L2gradient参数是一个布尔值,如果为真,则使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开放),否则使用L1范数(直接将两个方向导数的绝对值相加)。


  • 静态设置阈值
# 由于Canny只能处理灰度图,所以将读取的图像转成灰度图
img = cv2.imread("./photo/img.png", 0)
# 用高斯平滑处理原图像降噪后再进行边缘检测
img = cv2.GaussianBlur(img,(3,3),0)
# Canny函数的使用很简单,只需指定最大和最小阈值即可
canny = cv2.Canny(img, 50, 150)
cv2.imshow('Canny', canny)
cv2.waitKey(0)
cv2.destroyAllWindows()

image.png

  • 动态设置阈值
def CannyThreshold(lowThreshold):
    detected_edges = cv2.GaussianBlur(gray,(3,3),0)
    detected_edges = cv2.Canny(detected_edges,lowThreshold,lowThreshold*ratio,apertureSize = kernel_size)
    dst = cv2.bitwise_and(img,img,mask = detected_edges)  # just add some colours to edges from original image.
    cv2.imshow('canny demo',dst)
lowThreshold = 0
max_lowThreshold = 100
ratio = 3
kernel_size = 3
img = cv2.imread('./photo/img.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#创建拉伸条
cv2.namedWindow('canny demo')
cv2.createTrackbar('Min threshold','canny demo',lowThreshold, max_lowThreshold, CannyThreshold)
CannyThreshold(0)  # initialization
if cv2.waitKey(0) == 27:
    cv2.destroyAllWindows()

image.png


代码来源:https://github.com/abidrahmank/OpenCV2-Python/blob/master/Official_Tutorial_Python_Codes/3_imgproc/canny.py


8. 霍夫变换检测直线


Hough变换是经典的检测直线的算法。其最初用来检测图像中的直线,同时也可以将其扩展,以用来检测图像中简单的结构。


OpenCV提供了两种用于直线检测的Hough变换形式。其中基本的版本是cv2.HoughLines。其输入一幅含有点集的二值图(由非0像素表示),其中一些点互相联系组成直线。通常这是通过如Canny算子获得的一幅边缘图像。cv2.HoughLines函数输出的是[float, float]形式的ndarray,其中每个值表示检测到的线(ρ , θ)中浮点点值的参数。


下面的例子首先使用Canny算子获得图像边缘,然后使用Hough变换检测直线。其中HoughLines函数的参数3和4对应直线搜索的步长。在本例中,函数将通过步长为1的半径和步长为π/180的角来搜索所有可能的直线。最后一个参数是经过某一点曲线的数量的阈值,超过这个阈值,就表示这个交点所代表的参数对(rho, theta)在原图像中为一条直线。


详细内容见官方文档:http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.html


  • 标准霍夫线变换
Help on built-in function HoughLines:
HoughLines(...)
    HoughLines(image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]]) -> lines
img = cv2.imread("./photo/road.jpg", 0)
img = cv2.GaussianBlur(img,(3,3),0)
edges = cv2.Canny(img, 100, 150, apertureSize = 3)
# 标准霍夫线变换
lines = cv2.HoughLines(edges,1,np.pi/180,118)
# dst: 边缘检测的输出图像. 它应该是个灰度图 (但事实上是个二值化图)
# rho : 参数极径 r 以像素值为单位的分辨率. 我们使用 1 像素.
# theta: 参数极角 \theta 以弧度为单位的分辨率. 我们使用 1度 (即CV_PI/180)
# threshold: 要”检测” 一条直线所需最少的的曲线交点(里对最后一个参数使用了经验值118)
# 返回:lines: 储存着检测到的直线的参数对 (r,\theta) 的容器
# (a,1,b) -> (a, b)
lines = lines.reshape(lines.shape[0],lines.shape[-1])
# 通过画出检测到的直线来显示结果
for line in lines:
    rho = line[0]
    theta = line[1]
    a = np.cos(theta)
    b = np.sin(theta)
    point1_x = np.round(a*rho + 1000*(-b)).astype(int)
    point1_y = np.round(b*rho + 1000*(a)).astype(int)
    point2_x = np.round(a*rho - 1000*(-b)).astype(int)
    point2_y = np.round(b*rho - 1000*(a)).astype(int)
    point1 = (point1_x, point1_y)
    point2 = (point2_x, point2_y)
#     print(point1, point2)
    cv2.line(img, point1, point2, (255, 0, 0))
cv2.imshow('Canny', edges )
cv2.imshow('Result', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

image.png


  • 统计概率霍夫线变换
HoughLinesP(...)
    HoughLinesP(image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]]) -> lines
img = cv2.imread("./photo/road.jpg", 0)
img = cv2.GaussianBlur(img,(3,3),0)
edges = cv2.Canny(img, 100, 150, apertureSize = 3)
# 标准霍夫线变换
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 80, 100, 10)
# image: 边缘检测的输出图像. 它应该是个灰度图 (但事实上是个二值化图)
# rho : 参数极径 r 以像素值为单位的分辨率. 我们使用 1 像素.
# theta: 参数极角 \theta 以弧度为单位的分辨率. 我们使用 1度 (即CV_PI/180)
# threshold: 要”检测” 一条直线所需最少的的曲线交点(里对最后一个参数使用了经验值118)
# minLinLength: 能组成一条直线的最少点的数量. 点数量不足的直线将被抛弃.
# maxLineGap: 能被认为在一条直线上的亮点的最大距离.
# 返回:lines: 储存着检测到的直线的参数对 (x_{start}, y_{start}, x_{end}, y_{end}) 的容器
# (a,1,b) -> (a, b)
lines = lines.reshape(lines.shape[0],lines.shape[-1])
# 通过画出检测到的直线来显示结果
for line in lines:
    point1 = (line[0], line[1])  # x_{start}, y_{start}
    point2 = (line[2], line[3])  # x_{end},   y_{end}
    cv2.line(img, point1, point2, (0,0,255), 3)  # 这里的3控制线的粗细
cv2.imshow('Canny', edges )
cv2.imshow('Result', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

image.png


9. 直方图均衡化


在某些情况下,一副图像中大部分像素的强度都集中在某一区域,而质量较高的图像中,像素的强度应该均衡的分布。为此,可将表示像素强度的直方图进行拉伸,将其平坦化。如下:

image.png

def HistDisr_Show(image):
    HistSize = 256
    x = np.arange(0, HistSize)
    histImg = cv2.calcHist([image], [0], None, [HistSize], [0.0,255.0])  
    plt.figure(figsize=[10, 5])
    plt.plot(x, histImg.flatten(), color='blue')
    # 将坐标轴隐藏
    fig = plt.gca()
    fig.axes.get_xaxis().set_visible(False)
    fig.axes.get_yaxis().set_visible(False)
#     return histImg


  • 使用查找表来拉伸直方图

观察上图中原始图像的直方图,很容易发现大部分强度值范围都没有用到。因此先检测图像非0的最低(imin)强度值和最高(imax)强度值。将最低值imin设为0,最高值imax设为255。中间的按255.0*(i-imin)/(imax-imin)+0.5)的形式设置。

img = cv2.imread("./photo/outside.jpg", 0)
histImgB= cv2.calcHist([img], #计算图像的直方图
    [0], #使用的通道
    None, #没有使用mask
    [256], #it is a 1D histogram
    [0.0,255.0])
minBinNo, maxBinNo = 0, 255
lut = np.zeros(256, dtype = img.dtype )#创建空的查找表
#计算从左起第一个不为0的直方图柱的位置
for binNo, binValue in enumerate(histImgB):
    if binValue != 0:
        minBinNo = binNo
        break
#计算从右起第一个不为0的直方图柱的位置
for binNo, binValue in enumerate(reversed(histImgB)):
    if binValue != 0:
        maxBinNo = 255-binNo
        break
print(minBinNo, maxBinNo)
#生成查找表,方法来自参考文献1第四章第2节
for i,v in enumerate(lut):
#     print(i)
    if i < minBinNo:
        lut[i] = 0
    elif i > maxBinNo:
        lut[i] = 255
    else:
        lut[i] = int(255.0*(i-minBinNo)/(maxBinNo-minBinNo)+0.5)
#计算: cv2.LUT函数只有两个参数,分别为输入图像和查找表,其返回处理的结果
result = cv2.LUT(img, lut)
# HistDisr_Show(result)
cv2.imshow("Orgin", img)
cv2.imshow("Result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
86 246

image.png


可以看出,直方图结果如下,可以看到原来占的区域很小的直方图尖峰被移动了


HistDisr_Show(img)

image.png

HistDisr_Show(result)

image.png

  • 直方图均衡化

有时图像的视觉上的缺陷并不在强度值集中在很窄的范围内。而是某些强度值的使用频率很大。比如第一幅图中,灰度图中间值的占了很大的比例。

在完美均衡的直方图中,每个柱的值都应该相等。即50%的像素值应该小于128,25%的像素值应该小于64。总结出的经验可定义为:在标准的直方图中p%的像素拥有的强度值一定小于或等于255×p%。将该规律用于均衡直方图中:强度i的灰度值应该在对应的像素强度值低于i的百分比的强度中。其实,按我的理解来说们这也就是一个比率的问题。

lut
array([  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
         0,   0,   0,   0,   0,   0,   0,   0,   0,   2,   3,   5,   6,
         8,  10,  11,  13,  14,  16,  18,  19,  21,  22,  24,  26,  27,
        29,  30,  32,  33,  35,  37,  38,  40,  41,  43,  45,  46,  48,
        49,  51,  53,  54,  56,  57,  59,  61,  62,  64,  65,  67,  69,
        70,  72,  73,  75,  77,  78,  80,  81,  83,  84,  86,  88,  89,
        91,  92,  94,  96,  97,  99, 100, 102, 104, 105, 107, 108, 110,
       112, 113, 115, 116, 118, 120, 121, 123, 124, 126, 128, 129, 131,
       132, 134, 135, 137, 139, 140, 142, 143, 145, 147, 148, 150, 151,
       153, 155, 156, 158, 159, 161, 163, 164, 166, 167, 169, 171, 172,
       174, 175, 177, 179, 180, 182, 183, 185, 186, 188, 190, 191, 193,
       194, 196, 198, 199, 201, 202, 204, 206, 207, 209, 210, 212, 214,
       215, 217, 218, 220, 222, 223, 225, 226, 228, 230, 231, 233, 234,
       236, 237, 239, 241, 242, 244, 245, 247, 249, 250, 252, 253, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255], dtype=uint8)


p[i]即直方图累积值,这是包含小于给点强度值的像素的直方图,以代替包含指定强度值像素的数目。比如第一幅图像的累计直方图如下图中的蓝线,而完美均衡的直方图,其累积直方图应为一条斜线,如图中均衡化之后的红线。

image.png


更专业一点,这种累积直方图应称为累积分布(cumulative distribition)。在NumPy中有一个专门的函数来计算。

通过上面的介绍,应该可以明白,直方图均衡化就是对图像使用一种特殊的查询表。在第三个例子中可以看到使用查询表来获得直方图均衡化的效果。通常来说,直方图均衡化大大增加了图像的表象。但根据图像可视内容的不同,不同图像的直方图均衡化产生的效果不尽相同.

img = cv2.imread("./photo/outside.jpg", 0)
# 用OpenCV实现直方图均衡化很简单,只需调用一个函数即可
result = cv2.equalizeHist(img)
cv2.imshow("Orgin Image", img)
cv2.imshow("Opencv Result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

image.png


查看使用用OpenCV实现直方图均衡化函数的直方图分布,感觉图像好像锐化得比较严重。


HistDisr_Show(result)

image.png

  • 全图像直方图均衡化处理
img = cv2.imread("./photo/road.jpg")
# 通道分离
b, g, r = cv2.split(img)  
# 用OpenCV实现直方图均衡化很简单,只需调用一个函数即可
imgB = cv2.equalizeHist(b)
imgG = cv2.equalizeHist(g)
imgR = cv2.equalizeHist(r)
merged = cv2.merge([imgB, imgG, imgR])
cv2.imshow("img", img)
cv2.imshow("merge", merged)
cv2.waitKey(0)
cv2.destroyAllWindows()

image.png


10. 轮廓检测


OpenCV-Python接口中使用cv2.findContours()函数来查找检测物体的轮廓。


cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]])


参数:


第一个参数是寻找轮廓的图像;


第二个参数表示轮廓的检索模式,有四种(本文介绍的都是新的cv2接口):

cv2.RETR_EXTERNAL表示只检测外轮廓

cv2.RETR_LIST检测的轮廓不建立等级关系

cv2.RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。

cv2.RETR_TREE建立一个等级树结构的轮廓。


第三个参数method为轮廓的近似办法

cv2.CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1

cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息

cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法


返回值

cv2.findContours()函数返回两个值,一个是轮廓本身,还有一个是每条轮廓对应的属性。ps:opencv2返回两个值:contours:hierarchy。注:opencv3会返回三个值,分别是img, countours, hierarchy

img = cv2.imread("./photo/conter.jpg")
# 转成灰度图
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 二值图
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
# cv2.imshow("binary", binary)
# 轮廓检测
orginimg, contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# cv2.imshow("binary_after", binary)
# cv2.findContours()函数首先返回一个list,list中每个元素都是图像中的一个轮廓
# hierarchy本身包含两个ndarray,每个ndarray对应一个轮廓,每个轮廓有四个属性。
# orginimg是返回了你所处理的图像
# 分别用两种颜色绘制两个轮廓
cv2.drawContours(img,contours,0,(255,0,0),2)
cv2.drawContours(img,contours,1,(0,255,0),2)
# 第一个参数是指明在哪幅图像上绘制轮廓;
# 第二个参数是轮廓本身,在Python中是一个list。
# 第三个参数指定绘制轮廓list中的哪条轮廓,如果是-1,则绘制其中的所有轮廓。后面的参数很简单。其中thickness表明轮廓线的宽度,如果是-1(cv2.FILLED),则为填充模式。绘制参数将在以后独立详细介绍。
cv2.imshow("img", img)
# cv2.imshow("orgonimg", orginimg)
cv2.waitKey(0)
cv2.destroyAllWindows()

image.png


拓展:若出现ValueError: too many values to unpack 类错误,多为输入或者输出参数数量不一致导致。


有关cv2.findContours函数,cv2.drawContours函数的的详细介绍见:https://blog.csdn.net/sunny2038/article/details/12889059


  • 函数封装
def DrawContour(img):
# img = cv2.imread("./photo/conter.jpg")
    # 转成灰度图
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    # 二值图
    ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
    # 根据二值图返回轮廓列表
    # cv2.RETR_TREE:建立一个等级树结构的轮廓
    # cv2.CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标
    _, contours, _ = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
    # 绘制轮廓(参数-1表示绘制全部轮廓)
    cv2.drawContours(img,contours,-1,(255,0,0),2)
    # 轮廓已绘制在原图上,显示出来
    cv2.imshow("img", img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
img = cv2.imread("./photo/building.jpg")
DrawContour(img)

image.png


参考资料专栏:


OpenCV

OpenCV入门指南


拓展资料:


1. python——opencv入门

2. Python+OpenCV图像处理(一篇全)


Github参考资料:


ImageProcessing


目录
相关文章
|
14天前
|
数据采集 存储 XML
Python爬虫定义入门知识
Python爬虫是用于自动化抓取互联网数据的程序。其基本概念包括爬虫、请求、响应和解析。常用库有Requests、BeautifulSoup、Scrapy和Selenium。工作流程包括发送请求、接收响应、解析数据和存储数据。注意事项包括遵守Robots协议、避免过度请求、处理异常和确保数据合法性。Python爬虫强大而灵活,但使用时需遵守法律法规。
|
11天前
|
机器学习/深度学习 人工智能 TensorFlow
人工智能浪潮下的自我修养:从Python编程入门到深度学习实践
【10月更文挑战第39天】本文旨在为初学者提供一条清晰的道路,从Python基础语法的掌握到深度学习领域的探索。我们将通过简明扼要的语言和实际代码示例,引导读者逐步构建起对人工智能技术的理解和应用能力。文章不仅涵盖Python编程的基础,还将深入探讨深度学习的核心概念、工具和实战技巧,帮助读者在AI的浪潮中找到自己的位置。
|
11天前
|
机器学习/深度学习 数据挖掘 Python
Python编程入门——从零开始构建你的第一个程序
【10月更文挑战第39天】本文将带你走进Python的世界,通过简单易懂的语言和实际的代码示例,让你快速掌握Python的基础语法。无论你是编程新手还是想学习新语言的老手,这篇文章都能为你提供有价值的信息。我们将从变量、数据类型、控制结构等基本概念入手,逐步过渡到函数、模块等高级特性,最后通过一个综合示例来巩固所学知识。让我们一起开启Python编程之旅吧!
|
11天前
|
存储 Python
Python编程入门:打造你的第一个程序
【10月更文挑战第39天】在数字时代的浪潮中,掌握编程技能如同掌握了一门新时代的语言。本文将引导你步入Python编程的奇妙世界,从零基础出发,一步步构建你的第一个程序。我们将探索编程的基本概念,通过简单示例理解变量、数据类型和控制结构,最终实现一个简单的猜数字游戏。这不仅是一段代码的旅程,更是逻辑思维和问题解决能力的锻炼之旅。准备好了吗?让我们开始吧!
|
1天前
|
设计模式 缓存 开发者
Python中的装饰器:从入门到实践####
本文深入探讨了Python中强大的元编程工具——装饰器,它能够以简洁优雅的方式扩展函数或方法的功能。通过具体实例和逐步解析,文章不仅介绍了装饰器的基本原理、常见用法及高级应用,还揭示了其背后的设计理念与实现机制,旨在帮助读者从理论到实战全面掌握这一技术,提升代码的可读性、可维护性和复用性。 ####
|
10天前
|
设计模式 缓存 开发框架
Python中的装饰器:从入门到实践####
本文深入探讨了Python中装饰器的工作原理与应用,通过具体案例展示了如何利用装饰器增强函数功能、提高代码复用性和可读性。读者将学习到装饰器的基本概念、实现方法及其在实际项目开发中的实用技巧。 ####
22 3
|
14天前
|
机器学习/深度学习 数据采集 数据可视化
Python在数据科学中的应用:从入门到实践
本文旨在为读者提供一个Python在数据科学领域应用的全面概览。我们将从Python的基础语法开始,逐步深入到数据处理、分析和可视化的高级技术。文章不仅涵盖了Python中常用的数据科学库,如NumPy、Pandas和Matplotlib,还探讨了机器学习库Scikit-learn的使用。通过实际案例分析,本文将展示如何利用Python进行数据清洗、特征工程、模型训练和结果评估。此外,我们还将探讨Python在大数据处理中的应用,以及如何通过集成学习和深度学习技术来提升数据分析的准确性和效率。
|
13天前
|
机器学习/深度学习 数据挖掘 开发者
Python编程入门:理解基础语法与编写第一个程序
【10月更文挑战第37天】本文旨在为初学者提供Python编程的初步了解,通过简明的语言和直观的例子,引导读者掌握Python的基础语法,并完成一个简单的程序。我们将从变量、数据类型到控制结构,逐步展开讲解,确保即使是编程新手也能轻松跟上。文章末尾附有完整代码示例,供读者参考和实践。
|
13天前
|
人工智能 数据挖掘 程序员
Python编程入门:从零到英雄
【10月更文挑战第37天】本文将引导你走进Python编程的世界,无论你是初学者还是有一定基础的开发者,都能从中受益。我们将从最基础的语法开始讲解,逐步深入到更复杂的主题,如数据结构、面向对象编程和网络编程等。通过本文的学习,你将能够编写出自己的Python程序,实现各种功能。让我们一起踏上Python编程之旅吧!
|
7天前
|
机器学习/深度学习 存储 数据挖掘
Python 编程入门:理解变量、数据类型和基本运算
【10月更文挑战第43天】在编程的海洋中,Python是一艘易于驾驭的小船。本文将带你启航,探索Python编程的基础:变量的声明与使用、丰富的数据类型以及如何通过基本运算符来操作它们。我们将从浅显易懂的例子出发,逐步深入到代码示例,确保即使是零基础的读者也能跟上步伐。准备好了吗?让我们开始吧!
17 0
下一篇
无影云桌面