基于python语言和OpenCV环境的代码优化方向

问题遇到的现象和发生背景
问题相关代码,请勿粘贴截图
运行结果及报错内容
我的解答思路和尝试过的方法
我想要达到的结果

```python
import numpy as np
import cv2

def sort_contours(cnts, method="left-to-right"):
    reverse = False
    i = 0

    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True

    if method == "top-to-bottom" or method == "bottom-to-top":
        i = 1
    boundingBoxes = [cv2.boundingRect(c) for c in cnts] #用一个最小的矩形,把找到的形状包起来x,y,h,w
    #zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
    # * 号操作符,可以将元组解压为列表。
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))

    return cnts, boundingBoxes

def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
    dim = None
    (h, w) = image.shape[:2]
    if width is None and height is None:
        return image
    if width is None:
        r = height / float(h)
        dim = (int(w * r), height)
    else:
        r = width / float(w)
        dim = (width, int(h * r))
    resized = cv2.resize(image, dim, interpolation=inter)
    return resized

def cv_show(name,img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

# 读取一个模板图像
img= cv2.imread('images/ocr_a_reference.png')
cv_show('img',img)

#图像灰度化
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('ref',ref)

# 图像二值化
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
cv_show('ref',ref)

#数字定位
# 计算轮廓
#cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图),cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标
#返回的list中每个元素都是图像中的一个轮廓

ref_, refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

cv2.drawContours(img,refCnts,-1,(0,0,255),3) 
cv_show('img',img)
print (np.array(refCnts).shape)
refCnts = sort_contours(refCnts, method="left-to-right")[0] #排序,从左到右,从上到下

digits = {}
# 遍历每一个轮廓
for (i, c) in enumerate(refCnts):
    # 计算外接矩形并且resize成合适大小
    (x, y, w, h) = cv2.boundingRect(c)
    roi = ref[y:y + h, x:x + w]
    roi = cv2.resize(roi, (57, 88))

    # 每一个数字对应每一个模板
    digits[i] = roi
    cv_show('roi', digits[i])

# 初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

#读取输入图像,预处理:resize+灰度化
image= cv2.imread('images/credit_card_01.png')
cv_show('image',image)
image = resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv_show('gray',gray)

#礼帽操作,突出更明亮的区域
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) 
cv_show('tophat',tophat) 

#图像边缘检测
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, #ksize=-1相当于用3*3的
    ksize=-1)

gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")

print (np.array(gradX).shape)
cv_show('gradX',gradX)

#通过闭操作(先膨胀,再腐蚀)将数字连在一起
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel) 
cv_show('gradX',gradX)
#THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
thresh = cv2.threshold(gradX, 0, 255,
    cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
print(cv2.threshold(gradX, 0, 255,
    cv2.THRESH_BINARY | cv2.THRESH_OTSU)[0])
cv_show('thresh',thresh)

#再来一个闭操作

thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) #再来一个闭操作
cv_show('thresh',thresh)

# 计算轮廓
thresh_, threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE)

cnts = threshCnts
cur_img = image.copy()
cv2.drawContours(cur_img,cnts,-1,(0,0,255),3) 
cv_show('img',cur_img)

locs = []

# 遍历轮廓
for (i, c) in enumerate(cnts):
    # 计算矩形
    (x, y, w, h) = cv2.boundingRect(c)
    ar = w / float(h)

    # 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组
    if ar > 2.5 and ar < 4.0:
        if (w > 40 and w < 55) and (h > 10 and h < 20):
            #符合的留下来
            locs.append((x, y, w, h))
            
# 将符合的轮廓从左到右排序
locs = sorted(locs, key=lambda x:x[0])

output = []

# 遍历每一个轮廓中的数字
for (i, (gX, gY, gW, gH)) in enumerate(locs):
    # initialize the list of group digits
    groupOutput = []

    # 根据坐标提取每一个组
    group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
    cv_show('group',group)
    # 预处理
    group = cv2.threshold(group, 0, 255,
        cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    cv_show('group',group)
    # 计算每一组的轮廓
    group_,digitCnts,hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_SIMPLE)
    digitCnts = sort_contours(digitCnts,
        method="left-to-right")[0]

    # 计算每一组中的每一个数值
    for c in digitCnts:
        # 找到当前数值的轮廓,resize成合适的的大小
        (x, y, w, h) = cv2.boundingRect(c)
        roi = group[y:y + h, x:x + w]
        roi = cv2.resize(roi, (57, 88))
        cv_show('roi',roi)

        # 计算匹配得分
        scores = []

        # 在模板中计算每一个得分
        for (digit, digitROI) in digits.items():
            # 模板匹配
            result = cv2.matchTemplate(roi, digitROI,
                cv2.TM_CCOEFF)
            (_, score, _, _) = cv2.minMaxLoc(result)
            scores.append(score)

        # 得到最合适的数字
        groupOutput.append(str(np.argmax(scores)))

    # 画出来
    cv2.rectangle(image, (gX - 5, gY - 5),
        (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
    cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
        cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)

    # 得到结果
    output.extend(groupOutput)

# 指定信用卡类型
FIRST_NUMBER = {
    "3": "American Express",
    "4": "Visa",
    "5": "MasterCard",
    "6": "Discover Card"
}

# 打印结果
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)

```

算的慢,先看看哪里计算用去的时间比较多

这得把中间调试用的代码都注释掉,然后间隔添加一下时间统计代码,看看时间都耗在哪里了,然后才能根据代码实现的功能,看是否有优化的可能性

思路
1、先查看具体的函数,和每段代码之间的耗时多少,
2、然后再根据具体耗时来进行优化分析

解决方法
下面是我添加了耗时操作的,可以执行一下,看下耗时操作主要在哪里,发给我下,然后再进行具体的优化。
代码如下:

import numpy as np
import cv2
import time

import time


# 计算时间函数
def elapsed_time(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__qualname__, '耗时为:{} s'.format(end - start))
        return result

    return wrapper


@elapsed_time
def sort_contours(cnts, method="left-to-right"):
    reverse = False
    i = 0

    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True

    if method == "top-to-bottom" or method == "bottom-to-top":
        i = 1
    boundingBoxes = [cv2.boundingRect(c) for c in cnts]  # 用一个最小的矩形,把找到的形状包起来x,y,h,w
    # zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
    # * 号操作符,可以将元组解压为列表。
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))
    return cnts, boundingBoxes


@elapsed_time
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
    dim = None
    (h, w) = image.shape[:2]
    if width is None and height is None:
        return image
    if width is None:
        r = height / float(h)
        dim = (int(w * r), height)
    else:
        r = width / float(w)
        dim = (width, int(h * r))
    resized = cv2.resize(image, dim, interpolation=inter)
    return resized


@elapsed_time
def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


print("#######################")
start1 = time.time()
# 读取一个模板图像
img = cv2.imread('images/ocr_a_reference.png')
cv_show('img', img)
# 图像灰度化
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('ref', ref)
# 图像二值化
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
cv_show('ref', ref)
start2 = time.time()
print("这部分耗时为:", start2 - start1)

# 数字定位
# 计算轮廓
# cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图),cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标
# 返回的list中每个元素都是图像中的一个轮廓
print("#######################")
start3 = time.time()
ref_, refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, refCnts, -1, (0, 0, 255), 3)
cv_show('img', img)
print(np.array(refCnts).shape)
refCnts = sort_contours(refCnts, method="left-to-right")[0]  # 排序,从左到右,从上到下
start4 = time.time()
print("这部分耗时为:", start4 - start3)

print("#######################")
start5 = time.time()
digits = {}
# 遍历每一个轮廓
for (i, c) in enumerate(refCnts):
    # 计算外接矩形并且resize成合适大小
    (x, y, w, h) = cv2.boundingRect(c)
    roi = ref[y:y + h, x:x + w]
    roi = cv2.resize(roi, (57, 88))

    # 每一个数字对应每一个模板
    digits[i] = roi
    cv_show('roi', digits[i])
start6 = time.time()
print("这部分耗时为:", start6 - start5)

print("#######################")
start7 = time.time()

# 初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
start8 = time.time()
print("这部分耗时为:", start8 - start7)

print("#######################")
start9 = time.time()
# 读取输入图像,预处理:resize+灰度化
image = cv2.imread('images/credit_card_01.png')
cv_show('image', image)
image = resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv_show('gray', gray)
# 礼帽操作,突出更明亮的区域
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
cv_show('tophat', tophat)
start10 = time.time()
print("这部分耗时为:", start10 - start9)

print("#######################")
start11 = time.time()

# 图像边缘检测
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0,  # ksize=-1相当于用3*3的
                  ksize=-1)

gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")
print(np.array(gradX).shape)
cv_show('gradX', gradX)
# 通过闭操作(先膨胀,再腐蚀)将数字连在一起
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
cv_show('gradX', gradX)
# THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
thresh = cv2.threshold(gradX, 0, 255,
                       cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
print(cv2.threshold(gradX, 0, 255,
                    cv2.THRESH_BINARY | cv2.THRESH_OTSU)[0])
cv_show('thresh', thresh)
start12 = time.time()
print("这部分耗时为:", start12 - start11)

print("#######################")
start13 = time.time()
# 再来一个闭操作

thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)  # 再来一个闭操作
cv_show('thresh', thresh)

# 计算轮廓
thresh_, threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                                                  cv2.CHAIN_APPROX_SIMPLE)
cnts = threshCnts
cur_img = image.copy()
cv2.drawContours(cur_img, cnts, -1, (0, 0, 255), 3)
cv_show('img', cur_img)
locs = []
start14 = time.time()
print("这部分耗时为:", start14 - start13)

print("#######################")
start15 = time.time()

# 遍历轮廓
for (i, c) in enumerate(cnts):
    # 计算矩形
    (x, y, w, h) = cv2.boundingRect(c)
    ar = w / float(h)

    # 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组
    if ar > 2.5 and ar < 4.0:
        if (w > 40 and w < 55) and (h > 10 and h < 20):
            # 符合的留下来
            locs.append((x, y, w, h))

# 将符合的轮廓从左到右排序
locs = sorted(locs, key=lambda x: x[0])

output = []

# 遍历每一个轮廓中的数字
for (i, (gX, gY, gW, gH)) in enumerate(locs):
    # initialize the list of group digits
    groupOutput = []

    # 根据坐标提取每一个组
    group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
    cv_show('group', group)
    # 预处理
    group = cv2.threshold(group, 0, 255,
                          cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    cv_show('group', group)
    # 计算每一组的轮廓
    group_, digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
                                                    cv2.CHAIN_APPROX_SIMPLE)
    digitCnts = sort_contours(digitCnts,
                              method="left-to-right")[0]

    # 计算每一组中的每一个数值
    for c in digitCnts:
        # 找到当前数值的轮廓,resize成合适的的大小
        (x, y, w, h) = cv2.boundingRect(c)
        roi = group[y:y + h, x:x + w]
        roi = cv2.resize(roi, (57, 88))
        cv_show('roi', roi)
        # 计算匹配得分
        scores = []

        # 在模板中计算每一个得分
        for (digit, digitROI) in digits.items():
            # 模板匹配
            result = cv2.matchTemplate(roi, digitROI,
                                       cv2.TM_CCOEFF)
            (_, score, _, _) = cv2.minMaxLoc(result)
            scores.append(score)
        # 得到最合适的数字
        groupOutput.append(str(np.argmax(scores)))

    # 画出来
    cv2.rectangle(image, (gX - 5, gY - 5),
                  (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
    cv2.putText(image, "".join(groupOutput), (gX, gY - 15),
                cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)

    # 得到结果
    output.extend(groupOutput)

start16 = time.time()
print("这部分耗时为:", start16 - start15)

# 指定信用卡类型
FIRST_NUMBER = {
    "3": "American Express",
    "4": "Visa",
    "5": "MasterCard",
    "6": "Discover Card"
}

# 打印结果
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)

如有问题及时沟通。


start=time.time()
function()
end=time.time()
print("function用时:",end-start)

可以求函数function的执行时间,你把你的每一个函数调用时加上这一句就可以看看用时最长的函数是哪一个了,然后再调节,尽量用opencv或者numpy的内置函数

许多OpenCV功能都使用SSE2,AVX等进行了优化,当然它也包含未经优化的代码。 因此,如果我们的系统支持这些功能,我们应该利用它们(几乎所有现代处理器都支持它们).编译时默认启用它, 因此,OpenCV运行优化代码(如果已启用),否则运行未优化代码。 我们可以使用CV2.useOptimized()来检查它是否已启用/禁用,并使用CV2.setUseOptimized()来启用/禁用它

本次教程我们谈及OpenCV的性能衡量与优化,众所周知,算法的不断的革新其最重要的一点就是不断的优化再优化,比如我们的后面要讲到的边缘检测的算法,又或者是图像分割的算法,他们都是随着时间的一步一步的推移,从而完成算法层面的优化。在以后的学习中,我们会接触到诸多的框架,这些所谓的API他们都是固定的,如果我们只是单纯的调用这些API的话,那么就做不到算法层面的革新,而当我们自己想从原理层面来写这些算法的时候,我们就需要考虑到它的速度,是否能跟得上这些原有API的速度。

本次教程我们需要接触到几个函数:

cv.getTickCount()

调用此函数之后返回时钟周期的数目。因此,如果在函数执行之前和之后调用它,则会获得用于执行函数的时钟周期数。

cv.getTickFrequency()

函数返回时钟周期的频率,或每秒时钟周期的数目。

现在我们来做个实验,我们采用一个普通的高斯滤波器,调用OpenCV中的函数API,我们看看调用OpenCV的API速度怎么样:

import CV2  

  

img1 = CV2.imread("01.jpg")  

  

e1 = CV2.getTickCount()  

img1 = CV2.GaussianBlur(img1,(3,3),0)  

e2 = CV2.getTickCount()  

t = (e2 - e1) / CV2.getTickFrequency()  

print(t)  

CV2.imshow("res", img1)  

CV2.waitKey(0)  

CV2.destroyAllWindows()  

我们运行看看效果:

img

img

可以看到运行速度非常快,现在我们自己写一个高斯滤波器,不再调用官方API,我们看看效果,当然,这里只是进行一个实验,具体代码的编写目前并不要求大家掌握,这些是后面将要学到的内容,我们来看代码:

import CV2  

import numpy as np  

import math  

import copy  

  

gauss = np.array([121242121])  

  

  

def spilt(a):  

    if a / 2 == 0:  

        x1 = x2 = a / 2  

    else:  

        x1 = math.floor(a / 2)  

        x2 = a - x1  

    return -x1, x2  

  

  

def gaussian_b0x(a, b):  

    judge = 10  

    sum = 0  

    box = []  

    x1, x2 = spilt(a)  

    y1, y2 = spilt(b)  

    for i in range(x1, x2):  

        for j in range(y1, y2):  

            t = i * i + j * j  

            re = math.e ** (-t / (2 * judge * judge))  

            sum = sum + re  

            box.append(re)  

  

    box = np.array(box)  

    box = box / sum  

    # for x in box :  

    #     print (x)  

    return box  

  

  

def original(i, j, k, a, b, img):  

    x1, x2 = spilt(a)  

    y1, y2 = spilt(b)  

    temp = np.zeros(a * b)  

    count = 0  

    for m in range(x1, x2):  

        for n in range(y1, y2):  

            if i + m < 0 or i + m > img.shape[0] - 1 or j + n < 0 or j + n > img.shape[1] - 1:  

                temp[count] = img[i, j, k]  

            else:  

                temp[count] = img[i + m, j + n, k]  

            count += 1  

    return temp  

  

  

def gaussian_function(a, b, img, gauss_fun):  

    img0 = copy.copy(img)  

    for i in range(0, img.shape[0]):  

        for j in range(2, img.shape[1]):  

            for k in range(img.shape[2]):  

                temp = original(i, j, k, a, b, img0)  

                img[i, j, k] = np.average(temp, weights=gauss_fun)  # 按权分配  

    return img  

  

  

img0 = CV2.imread("01.jpg")  

gauss_new = gaussian_b0x(3 , 3)  

e1 = CV2.getTickCount()  

gauss_img = gaussian_function(33copy.copy(img0), copy.copy(gauss_new))  

e2 = CV2.getTickCount()  

t = (e2 - e1) / CV2.getTickFrequency()  

print(t)  

CV2.imshow("res",gauss_img)  

CV2.waitKey(0)  

CV2.destroyAllWindows()  


我们来看运行效果,跟官方API的效果一致:

img

但是其运行时间让人觉得难以接受,竟然长达18秒:

img

由此我们可以用这两个函数检验自己所写的算法是否已经足够优化,运行时间就是最好的证明。

OpenCV中的默认优化

许多OpenCV功能都使用SSE2,AVX等进行了优化,当然它也包含未经优化的代码。 因此,如果我们的系统支持这些功能,我们应该利用它们(几乎所有现代处理器都支持它们).编译时默认启用它, 因此,OpenCV运行优化代码(如果已启用),否则运行未优化代码。 我们可以使用CV2.useOptimized()来检查它是否已启用/禁用,并使用CV2.setUseOptimized()来启用/禁用它:

import CV2  

  

print(CV2.useOptimized())  

CV2.setUseOptimized(False)  

print(CV2.useOptimized())  


我们可以查看输出:

img

现在我们将其综合起来,看看优化之后与不优化的速度差异,我们先来看优化之后的:

import CV2  

  

img1 = CV2.imread("cat.jpg")  

print(CV2.useOptimized())  # True  

e1 = CV2.getTickCount()  

for i in range(5492):  

    img1 = CV2.medianBlur(img1i)  

e2 = CV2.getTickCount()  

print((e2 - e1) / CV2.getTickFrequency())  


时间:

img

我们再来看看未优化的效果:

import CV2  

  

img1 = CV2.imread("cat.jpg")  

CV2.setUseOptimized(False)  

print(CV2.useOptimized())  # False  

e1 = CV2.getTickCount()  

for i in range(5492):  

    img1 = CV2.medianBlur(img1i)  

e2 = CV2.getTickCount()  

print((e2 - e1) / CV2.getTickFrequency())  

img

事实证明,OpenCV在开启优化之后其运行速度得到了提升。

到本次教程为止,OpenCV入门篇的教程就结束了,从下一个教程开始,将进行OpenCV基础篇的讲解。