1.项目介绍

​ 项目为点钞机或者硬币计数器,实验使用卢比(印度)硬币作为测试对象,系统可实时检测到硬币面值。如果放置5卢比,它显示由0变成了5卢比,如果投入1卢比,它变成了6,如果再投入2卢比,它变成了8。所以系统主要区分三种不同面值硬币(5,2,1)。系统使用不同的技术去区分它,如果我们继续添加,它会继续的添加到总数中,不会得到错误的结果,可见该系统运行非常稳定。如果去掉5卢比,它会由34变成了29卢比,如果去掉2卢比,它变成27。这是一个非常有趣的项目,而且非常容易实施,而且很容易迁移到自己国家的货币并创建类似项目。

2.安装

2.1 安装opencv

要安装Python版的OpenCV,您可以按照以下步骤进行操作:

  1. 首先,确保您已经安装了Python解释器。您可以从Python官方网站(https://www.python.org/)下载并安装最新版本的Python。

  2. 打开命令行终端(Windows上是命令提示符或PowerShell,Mac和Linux上是终端)。

  3. 在命令行终端中输入以下命令来安装OpenCV的Python包(使用pip包管理器):

    pip install opencv-python
    

    如果您需要安装额外的功能模块,可以使用以下命令:

    pip install opencv-contrib-python
    

    这将安装OpenCV及其相关依赖项。

  4. 等待安装过程完成。安装完成后,您就可以在Python中使用OpenCV库了。

为了确认OpenCV是否已成功安装,您可以在Python交互式环境中尝试导入OpenCV模块:

import cv2
print(cv2.__version__)

如果成功导入并显示OpenCV的版本号,则表示安装成功。

2.2 安装 cvzone

pip install cvzone
import cvzone
print(cvzone.__version__)

如果成功导入并显示cvzone的版本号,则表示安装成功。

整体代码如下:

import cv2
import cvzone
import numpy as np
from cvzone.ColorModule import ColorFinder
cap = cv2.VideoCapture(0)
cap.set(3, 640)
cap.set(4, 480)
totalMoney = 0
myColorFinder = ColorFinder(False)
# Custom Orange Color
hsvVals = {'hmin': 0, 'smin': 0, 'vmin': 145, 'hmax': 63, 'smax': 91, 'vmax': 255}
def empty(a):
    pass 
cv2.namedWindow("Settings")
cv2.resizeWindow("Settings", 640, 240)
cv2.createTrackbar("Threshold1", "Settings", 219, 255, empty)
cv2.createTrackbar("Threshold2", "Settings", 233, 255, empty)
def preProcessing(img):
    imgPre = cv2.GaussianBlur(img, (5, 5), 3)
    thresh1 = cv2.getTrackbarPos("Threshold1", "Settings")
    thresh2 = cv2.getTrackbarPos("Threshold2", "Settings")
    imgPre = cv2.Canny(imgPre, thresh1, thresh2)
    kernel = np.ones((3, 3), np.uint8)
    imgPre = cv2.dilate(imgPre, kernel, iterations=1)
    imgPre = cv2.morphologyEx(imgPre, cv2.MORPH_CLOSE, kernel)
    return imgPre
while True:
    success, img = cap.read()
    imgPre = preProcessing(img)
    imgContours, conFound = cvzone.findContours(img, imgPre, minArea=20)

    totalMoney = 0
    imgCount = np.zeros((480, 640, 3), np.uint8)

    if conFound:
        for count, contour in enumerate(conFound):
            peri = cv2.arcLength(contour['cnt'], True)
            approx = cv2.approxPolyDP(contour['cnt'], 0.02 * peri, True)

           if len(approx) > 5:
                area = contour['area']
                x, y, w, h = contour['bbox']
                imgCrop = img[y:y + h, x:x + w]
                # cv2.imshow(str(count),imgCrop)
                imgColor, mask = myColorFinder.update(imgCrop, hsvVals)
                whitePixelCount = cv2.countNonZero(mask)
                # print(whitePixelCount)

                if area < 2050:
                    totalMoney += 5
                elif 2050 < area < 2500:
                    totalMoney += 1
                else:
                    totalMoney += 2

    # print(totalMoney)
    cvzone.putTextRect(imgCount, f'Rs.{totalMoney}', (100, 200),scale=10,offset=30,thickness=7)
    imgStacked = cvzone.stackImages([img, imgPre, imgContours,imgCount], 2, 1)
    cvzone.putTextRect(imgStacked, f'Rs.{totalMoney}', (50, 50))
    cv2.imshow("Image", imgStacked)
    # cv2.imshow("imgColor", imgColor)
    cv2.waitKey(1)

运行错误

cap = cv2.VideoCapture(0)      #由1改成0

3.摄像头

3.1 VideoCapture

import cv2
cap=cv2.VideoCapture(0)

我们从网络摄像机里获取图片,利用VideoCapture()获取图像。它接收一个参数来指定要读取的视频源,可以是视频文件或者摄像头索引号

0:代表默认摄像头(通常是内置摄像头),有时候用(-1),等效。
1:第二个链接的摄像头设备。
2:第三个链接的摄像头设备。
3.以此类推,当不确定摄像头索引,可尝试使用不同索引值来确定正确的摄像头设备。
下面是cv2.VideoCapture类的一些主要功能:
1.打开视频源:您可以使用cv2.VideoCapture来打开视频文件、摄像头设备或者网络视频流。通过提供相应的参数(如文件路径或摄像头索引),您可以指定要打开的视频源。
2.读取帧:使用read()方法可以逐帧读取视频。该方法返回两个值:一个布尔值,表示是否成功读取帧,以及读取的图像帧本身。
3.控制视频播放:您可以使用set()方法设置视频的属性,如帧宽度、帧高度、帧速率等。这使您能够控制视频的播放和捕获参数。
4.检测视频结束:通过检查read()方法返回的布尔值,您可以确定是否已经到达视频的末尾。
5.释放资源:在完成视频处理后,您可以使用release()方法释放相关的资源,如关闭视频文件或停止摄像头捕捉。
通过结合cv2.VideoCapture的功能,您可以编写代码来处理视频流、图像处理、实时分析等各种应用场景,包括视频录制、实时视频处理、人脸识别、物体检测等。
#捕获摄像头
import cv2
cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    cv2.imshow('Video', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()
#播放视频
import cv2
cap = cv2.VideoCapture(r'C:\Users\Victor\Downloads\Money_Coin Counter using Computer Vision(720p).mp4')
while cap.isOpened():
    ret, frame = cap.read()
    # 在这里进行帧处理操作
    cv2.imshow('Frame', frame)
    if cv2.waitKey(25) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()

该代码从名为”video.mp4”的视频文件中读取每一帧,并在名为”Frame”的窗口中显示。您可以在注释标记的位置添加帧处理代码,例如图像滤波、对象检测等。

import cv2
cap = cv2.VideoCapture('video.mp4')
# 设置播放速度为每秒25帧
cap.set(cv2.CAP_PROP_FPS, 25)
while cap.isOpened():
    ret, frame = cap.read()
    # 处理帧...
    cv2.imshow('Frame', frame)
    if cv2.waitKey(25) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()

if cv2.waitKey(1) & 0xFF == ord('q'):语句检查用户是否按下了键盘上的’q’键,如果按下了,则会中断循环并退出程序。

3.2 cv2.waitKey

3.2.1 如何实现5s自动关闭窗口

import cv2
import time
cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    cv2.imshow('Video', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
    # 设置关闭窗口的时间为5秒
    if time.time() - start_time >= 5:
        break
cap.release()
cv2.destroyAllWindows()

使用time.time()函数获取当前时间戳,并将其与开始时间进行比较。如果经过了5秒(或其他指定的时间),则跳出循环,从而关闭OpenCV窗口。

4.预处理

​ 接下来的一步是找到硬币的位置,所以我们需要找到这些硬币的形状,然后我们可以检查这些区域,我们也可以检查颜色,所以找到实际的硬币位置是只要的任务,我们可以使用contours方法,cvzone封装了contours函数,而且更加易用。

  • findContours()的第二个参数是二值图像(黑白两色),我们现在还没有二值图像,所以我们需要处理我们当前的图像,然后发送给查找轮廓函数。
import cvzone
cvzone.findContours(img,)

所以这里我们需要定义一个函数,在预处理图像中,我们将返回imgpre,第一步是增加一些blur,因为它太锐利时,有些轮廓会太锐利,而且噪声很容易被检测到,所以要增加一些模糊,加一些模糊会使其边缘变的平滑。我们添加高斯模糊,然后给我们给出模糊核的大小5*5,然后我们再给出sigma,这些是我们可以尝试的值。

def preProcessing(img):
     imgPre=cv2.GaussianBlur(img,(5,5),3)
     return imgPre
imgPre=preProcessing(img)
cv2.imshow('imgPre',img)


我们可以查看图像预处理结果。左侧是原图,右侧是模糊后的图,我们可以将它们放到一起。

我们可以使用cvzone里面的stackImages()函数进行图像堆叠。我们需要给出多少列,这里给的是2列,第三个图将排列到第二行,图像比例,我们保持原样,尺度参数我们设置为1,如果你想缩小比例,我们可以写0.5,0.7。

imgStacked=cvzone.stackImages([img,imgPre],2,1)

我们现在就有了一整副图像,这些图像堆叠到一起。这是图像预处理的第一步。第二步是Canny,我们需要找到边缘,我们需要用Canny()函数找到边缘,需要给出阈值。

imgPre=cv2.Canny(imgPre,150,200)

这里我们得到了获取边缘的图像。我们获得了不错的图像效果,但是我假想如果我们没有呢?我们该如何找到合适的值,我们可以设置一个trackingbar,分别跟踪第一个参数和第二个参数,可以通过滑动调节其值的大小,并看到实时的值,我们可以通过代码调整之前的值150改成50或200,然后再次运行他们,但是这是非常低效的。

我们可以新建一个窗口。

cv2.namedWindow("Settings")
cv2.resizeWindow("Settings",640,240)            #设置窗口大小,一定要保证和签名的名称一致
cv2.createTrackar("Threshold1","Settings",50,250,empty)      #保持一致,起始Value,最大值255,
cv2.createTrackar("Threshold2","Settings",100,250,empty)      #保持一致,起始Value,最大值255,

empty如果滑动块活动,将执行empty函数。

def empty(a):
  pass
thresh1=cv2.getTrackbarPos("Threshold1","Settings")
thresh2=cv2.getTrackbarPos("Threshold2","Settings")

新窗口我们将有两个轨道bar的新窗口,我们将实时的获取结果。滑动两个轨道bar,检测效果会发生变化。我可以调整它的位置,直到没有噪声,能看到很好的效果。这里你可以看到好的结果。我们可以放置不同的硬币,看一下效果。

我们可以设置其值为219,233,是比较好的值。

后面将进行膨胀,因为图像中可能有间隙,通过膨胀可以进行填充,我们还使用闭合操作,闭合任何打开的轮廓。因此这两种方法都将提供。

import numpy as np
kernel=np.ones((3,3),np.uint8)
imgPre=cv2.dilate(imgPre,kernel,iterations=1)

增强膨胀操作的强度,可以通过增加kernel的大小,也可以通过增加迭代的次数,你可以看出有非常好的效果。

如果有任何开放的小的区域,可以使用闭运算。

imgPre=cv2.morphologyEx(imgPre,cv2.MORPH_CLOSE)

这里我们获取到了比较好的边缘信息,分别经过高斯模糊,Canny(增加轨道bar),膨胀操作,闭运算。我们虽然直到了边缘,但是我们还是不知道其Location,但是我们不知道中心点在那里。

5.定位

我们不知道中心点在那里,不知道颜色和面积等信息。我们要找到这些定位,并根据这些定位找到他们的面积,我们用之前讨论的findcoutours,我们之前讨论的。这里最小面积我们限定了20。

imgContours,coFound=cvzone.findContours(img,imgPre,minArea=20)

这里我们也可以设置一个Trackbar,用来调节面积的值。

因为我们是控制的环境,我们可以设置一些硬编码的值,因为我们知道环境是受控的,相机到硬币的距离,是不变的。像素数在值方面是非常相似的。上面的函数会返回两个值,一个是imgContours(图像),conFound(轮廓)。

上面的这张图就是imgContours的结果,可以看到cvzone的imgContours的功能整合更加齐全。

这里的矩形我们称为边界框**,我们也可以得到中心点的位置,**现在它有点闪烁,但是总的来说,它是稳定的。我相信你稍微调整一些光照它会变得更加稳定。

6.检测圆形

接下来,我们要确定它是一个Circles,如果角的数量大于某一个值,意味着它是一个圆。如果是三个边,它就是三角形,如果是四个边,它是一个矩形或正方形。如果它大于任何一个值6,7,8,9,10,任何一个,那么它是一个圆,我们只想得到圆,所以图像中有矩形,我们不会检测到。

if conFound:   #如果由轮廓
   for contour in conFound:
    peri=cv2.arcLength(contour['cnt'],True)
    approx=cv2.approxPolyDP(contour['cnt'],0.02*peri,True)  #这两行是进行拟合。
    print(len(approx))    #角的数量

    if len(approx)>5:
       print(contour[])

进入findContours()函数,你可以看到它conFound里面包含,面积area,边框bbox,和中心点center。

因为conFound是个字典,contour[‘area’]可以提取面积的值。所以第一件事是检查的数量,我们把它粘贴都这里。

7.区分不同的硬币

我们可以通过area大小进行区分,也可以按照颜色进行区分, 如果area大于某个值,我们认为是最大的硬币。对于其他的两个我们可以使用颜色进行区分。把所有的硬币都拿走,放入一样的硬币,最大硬币,观察其面积的值。

8.颜色

但是我们仍然不能找到黄色和白色硬币的区别,许多国家有相同大小的硬币,不像这三个硬币这么好区分,在这种情况下,在这种情况下,你可以使用颜色对他们进行区分。

from cvzone.ColorModule import colorFinder

点击colorFinder可以进入函数内部。

myColorFinder = ColorFinder(False)

添加一个常用颜色。

# Custom Orange Color
hsvVals = {'hmin': 0, 'smin': 0, 'vmin': 145, 'hmax': 63, 'smax': 91, 'vmax': 255}

然后在主循环中,进行颜色查找。

 imgColor, mask = myColorFinder.update(imgCrop, hsvVals)

imgColor对应找到的颜色图像,其他的位置均为黑色,找到的颜色对应的位置为白色。

mask:无论发现什么颜色,它都将该颜色变为白色,我们可以计算白色像素点的数量。,如果有很多白色的点,说明我们检测到了该颜色。

然后将主循环中的False设置为True,这样可以允许我们修改颜色值,并找到实际的颜色。这种设置主要是为了测验。

myColorFinder = ColorFinder(True)

我们将单独显示此图像的颜色,

cv2.imshow("Image",imgColor)

我们将自动获取颜色的轨道bar,因为它包含在类中,一旦将ColorFinder设置为True。调节轨道bar,使得留下的颜色只有黄色,其他的颜色消失。

程序会自动打印出当前所选颜色的hsv的范围,将其从原来的程序中替换掉。关闭调试模式。

9.显示最后结果

imgCount = np.zeros((480, 640, 3), np.uint8)
cvzone.putTextRect(imgCount, f'Rs.{totalMoney}', (100, 200),scale=10,offset=30,thickness=7)