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


目录
相关文章
|
12天前
|
安全 数据处理 开发者
Python中的多线程编程:从入门到精通
本文将深入探讨Python中的多线程编程,包括其基本原理、应用场景、实现方法以及常见问题和解决方案。通过本文的学习,读者将对Python多线程编程有一个全面的认识,能够在实际项目中灵活运用。
|
7天前
|
数据采集 机器学习/深度学习 人工智能
Python编程入门:从基础到实战
【10月更文挑战第24天】本文将带你进入Python的世界,从最基础的语法开始,逐步深入到实际的项目应用。我们将一起探索Python的强大功能和灵活性,无论你是编程新手还是有经验的开发者,都能在这篇文章中找到有价值的内容。让我们一起开启Python的奇妙之旅吧!
|
9天前
|
数据采集 存储 数据库
Python中实现简单爬虫的入门指南
【10月更文挑战第22天】本文将带你进入Python爬虫的世界,从基础概念到实战操作,一步步指导你如何使用Python编写一个简单的网络爬虫。我们将不展示代码示例,而是通过详细的步骤描述和逻辑讲解,帮助你理解爬虫的工作原理和开发过程。无论你是编程新手还是有一定经验的开发者,这篇文章都将为你打开一扇通往数据收集新世界的大门。
|
7天前
|
测试技术 开发者 Python
探索Python中的装饰器:从入门到实践
【10月更文挑战第24天】 在Python的世界里,装饰器是一个既神秘又强大的工具。它们就像是程序的“隐形斗篷”,能在不改变原有代码结构的情况下,增加新的功能。本篇文章将带你走进装饰器的世界,从基础概念出发,通过实际例子,逐步深入到装饰器的高级应用,让你的代码更加优雅和高效。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往高效编程的大门。
|
9天前
|
存储 人工智能 数据挖掘
Python编程入门:构建你的第一个程序
【10月更文挑战第22天】编程,这个听起来高深莫测的词汇,实际上就像搭积木一样简单有趣。本文将带你走进Python的世界,用最浅显的语言和实例,让你轻松掌握编写第一个Python程序的方法。无论你是编程新手还是希望了解Python的爱好者,这篇文章都将是你的理想起点。让我们一起开始这段奇妙的编程之旅吧!
14 3
|
8天前
|
机器学习/深度学习 人工智能 算法
机器学习基础:使用Python和Scikit-learn入门
机器学习基础:使用Python和Scikit-learn入门
20 1
|
10天前
|
存储 程序员 开发者
Python编程入门:从零开始掌握基础语法
【10月更文挑战第21天】本文将带你走进Python的世界,通过浅显易懂的语言和实例,让你快速了解并掌握Python的基础语法。无论你是编程新手还是想学习一门新的编程语言,这篇文章都将是你的不二之选。我们将一起探索变量、数据类型、运算符、控制结构、函数等基本概念,并通过实际代码示例加深理解。准备好了吗?让我们开始吧!
|
1天前
|
开发者 Python
探索Python中的装饰器:从入门到实战
【10月更文挑战第30天】本文将深入浅出地介绍Python中一个强大而有趣的特性——装饰器。我们将通过实际代码示例,一步步揭示装饰器如何简化代码、增强函数功能并保持代码的可读性。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往更高效编程的大门。
|
10天前
|
数据采集 机器学习/深度学习 数据可视化
深入浅出:用Python进行数据分析的入门指南
【10月更文挑战第21天】 在信息爆炸的时代,掌握数据分析技能就像拥有一把钥匙,能够解锁隐藏在庞大数据集背后的秘密。本文将引导你通过Python语言,学习如何从零开始进行数据分析。我们将一起探索数据的收集、处理、分析和可视化等步骤,并最终学会如何利用数据讲故事。无论你是编程新手还是希望提升数据分析能力的专业人士,这篇文章都将为你提供一条清晰的学习路径。
|
11天前
|
调度 开发者 Python
探索Python中的异步编程:从入门到精通
在这个快节奏的技术时代,异步编程成为了提升应用性能的关键。本文将带你深入Python的异步编程世界,从基础概念到高级技巧,一探究竟。我们将一起学习如何利用Python的asyncio库来构建高效、响应迅速的异步应用。摘要部分,我们将以一个独特的视角,用一个简短的故事来吸引读者的兴趣,而不是传统的介绍性文字。