【手势识别学习笔记3】修正存储bug,以及录制需要的数据集

回顾

上一期我们留了一个小问题:设计好的录制工具并不能保存图片,每次调用cv2.imwrite()的时候返回值总是False,今天就来解决这个问题。

为什么会这样?

翻阅cv2.imwrite的官方文档,看到了对这个函数的限制:

Python: cv2.imwrite(filename, img[, params]) → retval

它的Parameters:

  • filename – Name of the file.
  • image – Image to be saved.
  • params

    Format-specific save parameters encoded as pairs paramId_1, paramValue_1, paramId_2, paramValue_2, ... . The following parameters are currently supported:

    • For JPEG, it can be a quality ( CV_IMWRITE_JPEG_QUALITY ) from 0 to 100 (the higher is the better). Default value is 95.
    • For PNG, it can be the compression level ( CV_IMWRITE_PNG_COMPRESSION ) from 0 to 9. A higher value means a smaller size and longer compression time. Default value is 3.
    • For PPM, PGM, or PBM, it can be a binary format flag ( CV_IMWRITE_PXM_BINARY ), 0 or 1. Default value is 1.

另外,文档中还说到,如果要保存JPG\PNG\TIFF格式的文档,需要是8或无符号16位图:

The function imwrite saves the image to the specified file. The image format is chosen based on the filename extension (see imread() for the list of extensions). Only 8-bit (or 16-bit unsigned (CV_16U) in case of PNG, JPEG 2000, and TIFF) single-channel or 3-channel (with ‘BGR’ channel order) images can be saved using this function. If the format, depth or channel order is different, use Mat::convertTo() , and cvtColor() to convert it before saving. Or, use the universal FileStorage I/O functions to save the image to XML or YAML format.

It is possible to store PNG images with an alpha channel using this function. To do this, create 8-bit (or 16-bit) 4-channel image BGRA, where the alpha channel goes last. Fully transparent pixels should have alpha set to 0, fully opaque pixels should have alpha set to 255/65535. The sample below shows how to create such a BGRA image and store to PNG file. It also demonstrates how to set custom compression parameters.

所以是图片格式的问题咯?

既然之前执行过cvtColor()把图片换到灰度空间上,那是不是不能保存就是格式的问题呢?

查阅cvtColor()支持的格式

回顾上次调用cvtColor()的时候,第二个参数指定的是cv2.COLOR_RGB2GRAY,我们或许可以看下cv2支持哪些格式。

在命令行下进入含有cv2的python环境,执行:

>>> import cv2
>>> print ([x for x in dir(cv2) if x.startswith('COLOR_')])

得到一大堆结果:

['COLOR_BAYER_BG2BGR', 'COLOR_BAYER_BG2BGRA', 'COLOR_BAYER_BG2BGR_EA', 'COLOR_BAYER_BG2BGR_VNG', 'COLOR_BAYER_BG2GRAY', 'COLOR_BAYER_BG2RGB', 'COLOR_BAYER_BG2RGBA', 'COLOR_BAYER_BG2RGB_EA', 'COLOR_BAYER_BG2RGB_VNG', 'COLOR_BAYER_GB2BGR', 'COLOR_BAYER_GB2BGRA', 'COLOR_BAYER_GB2BGR_EA', 'COLOR_BAYER_GB2BGR_VNG', 'COLOR_BAYER_GB2GRAY', 'COLOR_BAYER_GB2RGB', 'COLOR_BAYER_GB2RGBA', 'COLOR_BAYER_GB2RGB_EA', 'COLOR_BAYER_GB2RGB_VNG', 'COLOR_BAYER_GR2BGR', 'COLOR_BAYER_GR2BGRA', 'COLOR_BAYER_GR2BGR_EA', 'COLOR_BAYER_GR2BGR_VNG', 'COLOR_BAYER_GR2GRAY', 'COLOR_BAYER_GR2RGB', 'COLOR_BAYER_GR2RGBA', 'COLOR_BAYER_GR2RGB_EA', 'COLOR_BAYER_GR2RGB_VNG', 'COLOR_BAYER_RG2BGR', 'COLOR_BAYER_RG2BGRA', 'COLOR_BAYER_RG2BGR_EA', 'COLOR_BAYER_RG2BGR_VNG', 'COLOR_BAYER_RG2GRAY', 'COLOR_BAYER_RG2RGB', 'COLOR_BAYER_RG2RGBA', 'COLOR_BAYER_RG2RGB_EA', 'COLOR_BAYER_RG2RGB_VNG', 'COLOR_BGR2BGR555', 'COLOR_BGR2BGR565', 'COLOR_BGR2BGRA', 'COLOR_BGR2GRAY', 'COLOR_BGR2HLS', 'COLOR_BGR2HLS_FULL', 'COLOR_BGR2HSV', 'COLOR_BGR2HSV_FULL', 'COLOR_BGR2LAB', 'COLOR_BGR2LUV', 'COLOR_BGR2Lab', 'COLOR_BGR2Luv', 'COLOR_BGR2RGB', 'COLOR_BGR2RGBA', 'COLOR_BGR2XYZ', 'COLOR_BGR2YCR_CB', 'COLOR_BGR2YCrCb', 'COLOR_BGR2YUV', 'COLOR_BGR2YUV_I420', 'COLOR_BGR2YUV_IYUV', 'COLOR_BGR2YUV_YV12', 'COLOR_BGR5552BGR', 'COLOR_BGR5552BGRA', 'COLOR_BGR5552GRAY', 'COLOR_BGR5552RGB', 'COLOR_BGR5552RGBA', 'COLOR_BGR5652BGR', 'COLOR_BGR5652BGRA', 'COLOR_BGR5652GRAY', 'COLOR_BGR5652RGB', 'COLOR_BGR5652RGBA', 'COLOR_BGRA2BGR', 'COLOR_BGRA2BGR555', 'COLOR_BGRA2BGR565', 'COLOR_BGRA2GRAY', 'COLOR_BGRA2RGB', 'COLOR_BGRA2RGBA', 'COLOR_BGRA2YUV_I420', 'COLOR_BGRA2YUV_IYUV', 'COLOR_BGRA2YUV_YV12', 'COLOR_BayerBG2BGR', 'COLOR_BayerBG2BGRA', 'COLOR_BayerBG2BGR_EA', 'COLOR_BayerBG2BGR_VNG', 'COLOR_BayerBG2GRAY', 'COLOR_BayerBG2RGB', 'COLOR_BayerBG2RGBA', 'COLOR_BayerBG2RGB_EA', 'COLOR_BayerBG2RGB_VNG', 'COLOR_BayerGB2BGR', 'COLOR_BayerGB2BGRA', 'COLOR_BayerGB2BGR_EA', 'COLOR_BayerGB2BGR_VNG', 'COLOR_BayerGB2GRAY', 'COLOR_BayerGB2RGB', 'COLOR_BayerGB2RGBA', 'COLOR_BayerGB2RGB_EA', 'COLOR_BayerGB2RGB_VNG', 'COLOR_BayerGR2BGR', 'COLOR_BayerGR2BGRA', 'COLOR_BayerGR2BGR_EA', 'COLOR_BayerGR2BGR_VNG', 'COLOR_BayerGR2GRAY', 'COLOR_BayerGR2RGB', 'COLOR_BayerGR2RGBA', 'COLOR_BayerGR2RGB_EA', 'COLOR_BayerGR2RGB_VNG', 'COLOR_BayerRG2BGR', 'COLOR_BayerRG2BGRA', 'COLOR_BayerRG2BGR_EA', 'COLOR_BayerRG2BGR_VNG', 'COLOR_BayerRG2GRAY', 'COLOR_BayerRG2RGB', 'COLOR_BayerRG2RGBA', 'COLOR_BayerRG2RGB_EA', 'COLOR_BayerRG2RGB_VNG', 'COLOR_COLORCVT_MAX', 'COLOR_GRAY2BGR', 'COLOR_GRAY2BGR555', 'COLOR_GRAY2BGR565', 'COLOR_GRAY2BGRA', 'COLOR_GRAY2RGB', 'COLOR_GRAY2RGBA', 'COLOR_HLS2BGR', 'COLOR_HLS2BGR_FULL', 'COLOR_HLS2RGB', 'COLOR_HLS2RGB_FULL', 'COLOR_HSV2BGR', 'COLOR_HSV2BGR_FULL', 'COLOR_HSV2RGB', 'COLOR_HSV2RGB_FULL', 'COLOR_LAB2BGR', 'COLOR_LAB2LBGR', 'COLOR_LAB2LRGB', 'COLOR_LAB2RGB', 'COLOR_LBGR2LAB', 'COLOR_LBGR2LUV', 'COLOR_LBGR2Lab', 'COLOR_LBGR2Luv', 'COLOR_LRGB2LAB', 'COLOR_LRGB2LUV', 'COLOR_LRGB2Lab', 'COLOR_LRGB2Luv', 'COLOR_LUV2BGR', 'COLOR_LUV2LBGR', 'COLOR_LUV2LRGB', 'COLOR_LUV2RGB', 'COLOR_Lab2BGR', 'COLOR_Lab2LBGR', 'COLOR_Lab2LRGB', 'COLOR_Lab2RGB', 'COLOR_Luv2BGR', 'COLOR_Luv2LBGR', 'COLOR_Luv2LRGB', 'COLOR_Luv2RGB', 'COLOR_M_RGBA2RGBA', 'COLOR_RGB2BGR', 'COLOR_RGB2BGR555', 'COLOR_RGB2BGR565', 'COLOR_RGB2BGRA', 'COLOR_RGB2GRAY', 'COLOR_RGB2HLS', 'COLOR_RGB2HLS_FULL', 'COLOR_RGB2HSV', 'COLOR_RGB2HSV_FULL', 'COLOR_RGB2LAB', 'COLOR_RGB2LUV', 'COLOR_RGB2Lab', 'COLOR_RGB2Luv', 'COLOR_RGB2RGBA', 'COLOR_RGB2XYZ', 'COLOR_RGB2YCR_CB', 'COLOR_RGB2YCrCb', 'COLOR_RGB2YUV', 'COLOR_RGB2YUV_I420', 'COLOR_RGB2YUV_IYUV', 'COLOR_RGB2YUV_YV12', 'COLOR_RGBA2BGR', 'COLOR_RGBA2BGR555', 'COLOR_RGBA2BGR565', 'COLOR_RGBA2BGRA', 'COLOR_RGBA2GRAY', 'COLOR_RGBA2M_RGBA', 'COLOR_RGBA2RGB', 'COLOR_RGBA2YUV_I420', 'COLOR_RGBA2YUV_IYUV', 'COLOR_RGBA2YUV_YV12', 'COLOR_RGBA2mRGBA', 'COLOR_XYZ2BGR', 'COLOR_XYZ2RGB', 'COLOR_YCR_CB2BGR', 'COLOR_YCR_CB2RGB', 'COLOR_YCrCb2BGR', 'COLOR_YCrCb2RGB', 'COLOR_YUV2BGR', 'COLOR_YUV2BGRA_I420', 'COLOR_YUV2BGRA_IYUV', 'COLOR_YUV2BGRA_NV12', 'COLOR_YUV2BGRA_NV21', 'COLOR_YUV2BGRA_UYNV', 'COLOR_YUV2BGRA_UYVY', 'COLOR_YUV2BGRA_Y422', 'COLOR_YUV2BGRA_YUNV', 'COLOR_YUV2BGRA_YUY2', 'COLOR_YUV2BGRA_YUYV', 'COLOR_YUV2BGRA_YV12', 'COLOR_YUV2BGRA_YVYU', 'COLOR_YUV2BGR_I420', 'COLOR_YUV2BGR_IYUV', 'COLOR_YUV2BGR_NV12', 'COLOR_YUV2BGR_NV21', 'COLOR_YUV2BGR_UYNV', 'COLOR_YUV2BGR_UYVY', 'COLOR_YUV2BGR_Y422', 'COLOR_YUV2BGR_YUNV', 'COLOR_YUV2BGR_YUY2', 'COLOR_YUV2BGR_YUYV', 'COLOR_YUV2BGR_YV12', 'COLOR_YUV2BGR_YVYU', 'COLOR_YUV2GRAY_420', 'COLOR_YUV2GRAY_I420', 'COLOR_YUV2GRAY_IYUV', 'COLOR_YUV2GRAY_NV12', 'COLOR_YUV2GRAY_NV21', 'COLOR_YUV2GRAY_UYNV', 'COLOR_YUV2GRAY_UYVY', 'COLOR_YUV2GRAY_Y422', 'COLOR_YUV2GRAY_YUNV', 'COLOR_YUV2GRAY_YUY2', 'COLOR_YUV2GRAY_YUYV', 'COLOR_YUV2GRAY_YV12', 'COLOR_YUV2GRAY_YVYU', 'COLOR_YUV2RGB', 'COLOR_YUV2RGBA_I420', 'COLOR_YUV2RGBA_IYUV', 'COLOR_YUV2RGBA_NV12', 'COLOR_YUV2RGBA_NV21', 'COLOR_YUV2RGBA_UYNV', 'COLOR_YUV2RGBA_UYVY', 'COLOR_YUV2RGBA_Y422', 'COLOR_YUV2RGBA_YUNV', 'COLOR_YUV2RGBA_YUY2', 'COLOR_YUV2RGBA_YUYV', 'COLOR_YUV2RGBA_YV12', 'COLOR_YUV2RGBA_YVYU', 'COLOR_YUV2RGB_I420', 'COLOR_YUV2RGB_IYUV', 'COLOR_YUV2RGB_NV12', 'COLOR_YUV2RGB_NV21', 'COLOR_YUV2RGB_UYNV', 'COLOR_YUV2RGB_UYVY', 'COLOR_YUV2RGB_Y422', 'COLOR_YUV2RGB_YUNV', 'COLOR_YUV2RGB_YUY2', 'COLOR_YUV2RGB_YUYV', 'COLOR_YUV2RGB_YV12', 'COLOR_YUV2RGB_YVYU', 'COLOR_YUV420P2BGR', 'COLOR_YUV420P2BGRA', 'COLOR_YUV420P2GRAY', 'COLOR_YUV420P2RGB', 'COLOR_YUV420P2RGBA', 'COLOR_YUV420SP2BGR', 'COLOR_YUV420SP2BGRA', 'COLOR_YUV420SP2GRAY', 'COLOR_YUV420SP2RGB', 'COLOR_YUV420SP2RGBA', 'COLOR_YUV420p2BGR', 'COLOR_YUV420p2BGRA', 'COLOR_YUV420p2GRAY', 'COLOR_YUV420p2RGB', 'COLOR_YUV420p2RGBA', 'COLOR_YUV420sp2BGR', 'COLOR_YUV420sp2BGRA', 'COLOR_YUV420sp2GRAY', 'COLOR_YUV420sp2RGB', 'COLOR_YUV420sp2RGBA', 'COLOR_mRGBA2RGBA']

相当壮观啊!

所以只要把GRAY空间上的图片换回RGB就行了吗?

才怪嘞(逃

真·解决不能保存的bug

后来发现,原来不能保存的原因仅仅是桌面上没有目标文件夹……(囧)

修改保存图片的部分:

@staticmethod
def save_image(self, image, name):
    if self.__save_count is 100:
        self.__save_count = 0
        self.__is_now_saving = False
        print('Done!')
        return
    
    else:
        print('Saving ' + name + ' for #' + str(self.__save_count))
        pic_name = name + '_' + str(self.__save_count)
        folderpath = 'C:\\Users\\LeonWong\\Desktop\\' + name
        # 在这里创建文件夹
        os.makedirs(folderpath, 0o777, True)
        path = folderpath + '\\' + pic_name + '.jpg'
        print('Path: ' + path)
        flag = cv2.imwrite(path, image)
        print('Saved? ' + str(flag))
        self.__save_count += 1
        time.sleep(0.1)

再试一下,保存成功啦。

录制需要的手势

现在小公举工具已经完成了,下一步就是要用它来创建需要的数据集。

再一次提醒,请先手动修改代码中的保存路径

打开小工具,切换到二值化模式(按2):

感谢老李头友情出镜,肖像已授权,原创图片转载请先联系本公众号(或https://leonwong.cn)。

然后按3开始新的录制,小工具会录制100张手势照片,放在桌面上。

打开一个666的手势,可以看到二值化后的录制结果,图片大小为200 * 200:

录制几组手势后就可以开始训练了。

预告!

下一篇就要开始使用TensorFlow进行神经网络的训练了,初步计划采用CNN,对200*200的手势图片进行四次卷积(当然计划有可能会变动),预计本周末开始做,敬请期待!

【手势识别学习笔记2】利用摄像头获取数据集(下)

上一篇我们讲到了使用OpenCV框架帮助我们调用摄像头进行拍照,今天我们就要开始利用这个方法创建一个小工具,帮助我们产生需要的数据集。

小工具界面设计

在上一篇的讲解中,我们知道OpenCV有一个非常好用的函数:

cv2.waitKey()

通过这个函数,我们能够等待用户从键盘输入数据,那么很自然地,我们可以创建一个实时预览的窗口,并通过提供按键切换功能的方式,为我们创建一个简单的数据集录制工具。

另外,为了方便地把这个工具放进最终的工具包里,我们可以把它封装进class。

选项

主界面的设计比较简单,只需要显示实时的视频(请回顾上一期)和几个必要的选项。

  • 1:RGB模式
  • 2:Binary模式
  • 3:录制手势
  • 0:退出

大致来说这几个就够了。

那么这几个文字放在哪里呢?

cv2为我们提供了

cv2.putText()

函数,函数的作用是在拍摄的frame上显示文字。

为了方便可以把这一部分抽出来做一个函数:

@staticmethod
def show_capturer_options(frame):
 font_face = cv2.FONT_HERSHEY_COMPLEX
 font_size = 0.5
 font_color = (0, 255, 0)
 cv2.putText(frame, 'Options:', (18, 18), font_face, font_size, font_color)
 cv2.putText(frame, '1: RGB Mode', (18, 18 + 18), font_face, font_size, font_color)
 cv2.putText(frame, '2: Binary Mode', (18, 18 + 2 * 18), font_face, font_size, font_color)
 cv2.putText(frame, '3: New Gesture', (18, 18 + 3 * 18), font_face, font_size, font_color)
 cv2.putText(frame, '0: Quit', (18, 18 + 4 * 18), font_face, font_size, font_color)

解释一下代码:

font_face是cv2提供的字体选项,cv2.FONT_HERSHEY_SIMPLEX是正常大小无衬线字体。

font_size是字体缩放

font_color是RGB绿色。

然后依次按顺序把选项文字写在frame上即可。

显示主界面

利用上一篇中讲到的

cv2.imshow()

就可以显示主界面了,然后利用循环就能实时显示录制的内容了。

主循环代码如下:

def mainloop(self):
    while True:
        flag, frame = self.__cap.read()
        frame = cv2.flip(frame, 2)
        
        GestureCapturer.show_capturer_options(frame)
        key = cv2.waitKey(1) & 0xFF
        
        if key is ord('0'):
            self.quit_capturer()
            return
        
        cv2.imshow('Video Capturer', frame)

另外在退出的时候需要关闭窗口,所以再写一个函数用于退出

def quit_capturer(self):
    self.__cap.release()
    cv2.destroyAllWindows()

那么拍摄器的界面就完成了。

看一下全部的代码吧:

class GestureCapturer(object):
    
    def __init__(self):
        self.__cap = cv2.VideoCapture(0)
        
    @staticmethod
    def show_capturer_options(frame):
        font_face = cv2.FONT_HERSHEY_COMPLEX
        font_size = 0.5
        font_color = (0, 255, 0)
        cv2.putText(frame, 'Options:', (18, 18), font_face, font_size, font_color)
        cv2.putText(frame, '1: RGB Mode', (18, 18 + 18), font_face, font_size, font_color)
        cv2.putText(frame, '2: Binary Mode', (18, 18 + 2 * 18), font_face, font_size, font_color)
        cv2.putText(frame, '3: New Gesture', (18, 18 + 3 * 18), font_face, font_size, font_color)
        cv2.putText(frame, '0: Quit', (18, 18 + 4 * 18), font_face, font_size, font_color)
        
    def mainloop(self):
        while True:
            flag, frame = self.__cap.read()
            frame = cv2.flip(frame, 2)
        
            GestureCapturer.show_capturer_options(frame)
            key = cv2.waitKey(1) & 0xFF
        
            if key is ord('0'):
                self.quit_capturer()
                return
        
            cv2.imshow('Video Capturer', frame)
            
    def quit_capturer(self):
        self.__cap.release()
        cv2.destroyAllWindows()

效果也很不错:

需要注意的是,这个界面必须按0退出,点击右上角是不行的。(原因就在mainloop里面,欢迎各位自行修改代码)。

加入录制区域和录制功能

现在我们的主界面已经完成了,但是还只能显示摄像头捕获的画面,我们要为它加上拍摄功能。

在主界面框出录制区域

没有必要录制整个摄像头的画面,主要在于:

    1. 整个摄像头的画面太大了,实际上在使用摄像头的过程中,手出现的位置并不是很大,而且保存很大的图片会提高后期训练的难度。(电脑吃不消啊
    2. 摄像头的分辨率并不统一,这会影响代码的可移植性。

因此要对录制画面进行裁剪。

首先我们要在录制的画面上框出录制区域,cv2提供了一个

cv2.rectangle()

函数,能直接帮助我们画出一个正方形。

GestureCapturer里面添加一个名为draw_saving_area的函数:

@staticmethod
def draw_saving_area(frame):
    cv2.rectangle(frame, (100, 100), (300, 300), (0, 255, 0))

并且在mainloop中调用它(只要在imshow之前进行处理即可)。

现在我们就画出了从(100, 100)到(300, 300)的线段为对角线的正方形,以后就以这个位置作为录制区域了。

处理录制的图像

现在我们框出了录制区域,为了方便,可以把录制区域单独拿出来显示在一个小窗口里,同时,添加显示RGB模式和Binary模式切换的功能。

继续添加函数:

@staticmethod
def binary_mask(frame):
    area = frame[100:300, 100:300]
    gray = cv2.cvtColor(area, cv2.COLOR_RGB2GRAY)
    blur = cv2.GaussianBlur(gray, (5, 5), 2)
    at = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
    flag, bin = cv2.threshold(at, 70, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    return bin

这段代码可以说相当复杂,我们一条一条来看:

area = frame[100:300, 100:300]

是把我们要录制的区域扣了出来,这一步比较好理解(也就这一步比较好理解……)

gray = cv2.cvtColor(area, cv2.COLOR_RGB2GRAY)

cvtColor提供了一个颜色空间转换的API,指定第二个参数为COLOR_RGB2GRAY,很显然是将RGB空间上的色彩转换为灰度。

但是直接使用这样的转换是有问题的,所以引入了下面的处理方法:

高斯模糊

先说低通滤波器。低通滤波器主要的作用是降低图片的变化率,这样就能平滑并替代那些强度变化明显的区域。低通滤波器的算法可以是将每个像素替换为周围像素的均值。

但是有些时候,需要对一个像素的周围的像素给予更多的重视,这时候就需要重新分配权重,例如使用高斯函数来分配权重。

高斯模糊实际上也是一种低通滤波器。

blur = cv2.GaussianBlur(gray, (5, 5), 2)

其中,参数(5, 5)是高斯模糊矩阵(半径等参数),2是标准差,二者越大,图片的模糊程度越高。

自适应阀值二值化和固定阀值二值化

二值化操作是对一个灰度图片进行二值化处理,亦即转化为黑白两色。

固定阀值二值化是对单个像素超过阀值后进行的处理。

自适应阈值二值化函数根据图片一小块区域的值来计算对应区域的阈值,从而得到新的图片。

先简介一下固定阀值二值化:

固定阀值二值化处理有多种操作类型,cv2提供了:

  • cv2.THRESH_BINARY
  • cv2.THRESH_BINARY_INV
  • cv2.THRESH_TRUNC
  • cv2.THRESH_TOZERO
  • cv2.THRESH_TOZERO_INV

对应的具体效果如下图:

图片非原创,侵删

而自适应阀值二值化提供的方法更为可靠,它的参数是:

cv2.adaptiveThreshold(src, maxval, thresh_type, type, Block Size, C)
  • src: 输入图,只能输入单通道图像,通常来说为灰度图。
  • maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值。
  • thresh_type: 阈值的计算方法,包含以下2种类型:cv2.ADAPTIVE_THRESH_MEAN_C; cv2.ADAPTIVE_THRESH_GAUSSIAN_C.
  • type:二值化操作的类型,与固定阈值函数相同,包含以下5种类型: cv2.THRESH_BINARY; cv2.THRESH_BINARY_INV; cv2.THRESH_TRUNC; cv2.THRESH_TOZERO;cv2.THRESH_TOZERO_INV.
  • Block Size: 图片中分块的大小
  • C :阈值计算方法中的常数项

我们的代码使用了:

at = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)

进行了自适应阀值二值化。

OTSU:基于直方图的二值化

这又是一种二值化方法,简单来看,OTSU进行了这样的过程:

  1. 计算图像直方图
  2. 通过设定的阀值,将图像的像素分为两组。
  3. 分别计算两组内的偏移数,并把偏移数相加。
  4. 重复1-3的步骤,直到得到最小偏移数,其所对应的值即为结果阈值。

有关OTSU的介绍很多,这里就不在赘述了。

flag, bin = cv2.threshold(at, 70, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

最后就得到了二值化后的图像。

完成!

现在我们有了录制窗口,也有了二值化功能,还需要做的事情有:

  1. 单独显示截取的图片
  2. 命名和保存截取的图片

这两个都比较简单。

单独显示截取的区域

很简单,直接上代码:

def mainloop(self):
    is_binary_mode = False
    while True:
        ret, frame = self.__cap.read()
        frame = cv2.flip(frame, 2)
        
        GestureCapturer.show_capturer_options(frame)
        key = cv2.waitKey(1) & 0xFF
        
        if key is ord('0'):
            self.quit_capturer() 
            return
        
        if key is ord('1'):
            is_binary_mode = False
            
        if key is ord('2'):
            is_binary_mode = True
            
        GestureCapturer.draw_saving_area(frame)
        
        if is_binary_mode:
            binary_area = GestureCapturer.binary_mask(frame)
            cv2.imshow('Captured Area', binary_area)
            
        else:
            cv2.imshow('Captured Area', frame[100:300, 100:300])
            
        cv2.imshow('Video Capturer', frame)

设置保存功能

保存功能是当用户按下‘3’键的时候,提示用户选择一个名称,然后保存100帧。

保存功能使用

cv2.imwrite()

即可。(目前在我这里有些bug,保存的部分会另外介绍)

最后贴一下今天全部的代码吧

import cv2
import time
import os


class GestureCapturer(object):

    def __init__(self):
        self.__cap = cv2.VideoCapture(0)
        self.__is_now_saving = False
        self.__save_count = 0
        self.__name = 'Default Name'

    @staticmethod
    def show_capturer_options(frame):
        font_face = cv2.FONT_HERSHEY_SIMPLEX
        font_size = 0.5
        font_color = (0, 255, 0)
        cv2.putText(frame, 'Options:', (18, 18), font_face, font_size, font_color)
        cv2.putText(frame, '1: RGB Mode', (18, 18 + 18), font_face, font_size, font_color)
        cv2.putText(frame, '2: Binary Mode', (18, 18 + 2 * 18), font_face, font_size, font_color)
        cv2.putText(frame, '3: New Gesture', (18, 18 + 3 * 18), font_face, font_size, font_color)
        cv2.putText(frame, '0: Quit', (18, 18 + 4 * 18), font_face, font_size, font_color)

    def mainloop(self):
        is_binary_mode = False
        self.__is_now_saving = False
        while True:
            ret, frame = self.__cap.read()
            frame = cv2.flip(frame, 2)

            GestureCapturer.show_capturer_options(frame)
            key = cv2.waitKey(1) & 0xFF

            if key is ord('0'):
                self.quit_capturer()
                return

            if key is ord('1'):
                is_binary_mode = False

            if key is ord('2'):
                is_binary_mode = True

            if key is ord('3'):
                self.__is_now_saving = True
                self.__name = input('Input gesture name:')
                self.__save_count = 0

            GestureCapturer.draw_saving_area(frame)

            if is_binary_mode:
                binary_area = GestureCapturer.binary_mask(frame)
                cv2.imshow('Captured Area', binary_area)
                if self.__is_now_saving:
                    self.save_image(self, binary_area, self.__name)

            else:
                cv2.imshow('Captured Area', frame[100:300, 100:300])
                if self.__is_now_saving:
                    self.save_image(self, frame[100:300, 100:300], self.__name)

            cv2.imshow('Video Capturer', frame)

    def quit_capturer(self):
        self.__cap.release()
        cv2.destroyAllWindows()

    @staticmethod
    def draw_saving_area(frame):
        cv2.rectangle(frame, (100, 100), (300, 300), (0, 255, 0))

    @staticmethod
    def binary_mask(frame):
        area = frame[100:300, 100:300]
        gray = cv2.cvtColor(area, cv2.COLOR_RGB2GRAY)
        blur = cv2.GaussianBlur(gray, (5, 5), 2)
        at = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
        flag, bin = cv2.threshold(at, 70, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
        return bin

    @staticmethod
    def save_image(self, image, name):
        if (self.__save_count is 100):
            self.__save_count = 0
            self.__is_now_saving = False
            print('Done!')
            return

        else:
            print('Saving ' + name + ' for #' + str(self.__save_count))
            pic_name = name + str(self.__save_count)
            # 这里的路径需要注意
            path = 'C:\\Users\\LeonWong\\Desktop\\' + name + '\\' + pic_name + '.jpg'
            print('Path: ' + path)
            flag = cv2.imwrite(path, image)
            print('Saved? ' + str(flag))
            self.__save_count += 1
            time.sleep(0.1)


vc = GestureCapturer()
vc.mainloop()

【实时手势识别学习笔记1】使用OpenCV调用摄像头获取数据集(上)

今天是2008年5月12日汶川大地震10周年,逝者安息。

 

在完成了环境搭建(具体请翻阅实时手势识别学习笔记第0篇)后,就可以着手搭建学习框架咯。不过由于我们组决定实现的是手语教学,所以需要让机器学习很多的我们自定义的手势,显然这个很难通过从网络获得的数据实现,因此我们有必要通过自己的摄像头来获取手势。同时,因为要拍摄用户的手势,同样要用到摄像头,总之,我们选择了OpenCV框架来帮我们实现这个功能。

关于cv2.VideoCapture

VideoCapture是一个类,构造时需要传递一个Object参数,作为视频的来源。在这里使用0作为参数,则VideoCapture从默认摄像头获取图像。

cap = cv2.VideoCapture(0)

然后可以通过摄像头的指示灯判断摄像头是否开启(逃

其实是可以通过:

cap.isOpened()

来判断,在命令行下如果输出了True,就可以判断摄像头已经开启了。

最后记得使用

cap.release()

释放摄像头。

使用VideoCapture拍摄一张照片

打开命令行(如果是根据第0篇搭建的环境,请进入Anaconda Prompt),然后进入python环境,并执行:

import cv2
cap = cv2.VideoCapture(0)

然后执行

flag, frame = cap.read()

cap.read()提供给我们两个返回值,第一个记为flag,第二个记为frame。

flag保存了这一次read有没有成功读到一帧的信息,是一个布尔值,frame则是截取的帧。

在命令行下查看它们:

>>> flag
>>> frame

可以看到flag为True表示截取成功,frame是一个大矩阵,看起来是一张图片的样子。

然后保存这张照片:

cv2.imwrite("C:\\Users\\LeonWong\\Desktop\\a.jpg", frame)

可以看到照片被保存在桌面上了(为了不让我英俊帅气的脸露在网上,照片进行了处理)<-噫

用窗口显示实时视频

现在我们实现了拍照,那么照片连起来就是视频啦。

同时,为了避免输出到文件这么麻烦,我们选择把视频输出到屏幕。

打开IDE执行下面的代码:

import cv2 

# 调用摄像头 
cap = cv2.VideoCapture(0) 

while True: 
    # 读取一帧
    flag, frame = cap.read()
    # [可选] 把图像翻转过来,第二个参数为2意为沿y轴翻转
    frame = cv2.flip(frame, 2)
    key = cv2.waitKey(1) & 0xFF

    if flag:
        cv2.imshow("Camera Capture", frame)

    if key is ord('q'): 
        break


cap.release()
cv2.destroyAllWindows() 

 

其中,cv2.waitKey(1)是等待用户按下键盘,如果用户按下了键盘,判断这个键是q键,就退出程序。参数1是waitKey的等待时间。

cv2.imshow为我们提供了一个将图像显示到窗口的功能,它的第一个参数是窗口的标题,第二个参数是窗口显示的图像,窗口的大小会根据图像的大小进行调整。

最后通过cv2.destroyAllWindows()销毁所有窗口。

欢迎大家观赏我的床板(逃

【消失了800年之后突然跳出来】实时手势识别学习笔记0-环境搭建

为了完(ying)成(fu)HCI的项目,报了两位大佬的大腿,打算借此机会学习一下手势识别。

第0篇当然是进行环境搭建了~(因为可以凑出来一篇内容)

好了来看一下项目概要和环境要求:

项目概要

这次的项目是我HCI课程的项目的一部分,主要目的是通过一个摄像头实现实时的手势识别

为什么只写项目的一部分呢?因为项目除了手势识别还要干什么还没想好(逃

有可能是实现一个小游戏吧

咳咳说远了,回到我们的主题

既然是使用摄像头的手势识别,肯定要基于低分辨率(毕竟现在主流的电脑摄像头基本上都是1到2MP的),并且是2D的图像,那么就会想到采用OpenCV来进行录制。

其次,为了进行训练,当然要采用主流的(至少是我很喜欢使用的)Tensorflow框架。

项目环境

既然上面都说到了主要利用到的包,那么就列一下:

  • OpenCV,版本2.4.13.6,是CV2的最新版(截止时间为2018年5月1日,下同)
  • Python,版本3.6.5(采用了Anaconda 3 x64发行版,便于管理更多需要的包)
  • Tensorflow-GPU,版本1.8.0

其他可能用到的包(直接使用Anaconda管理)

因为我的项目还没有完成,我不太确定是否还会增加别的包,就目前来看,使用Anaconda来管理下面的包都是没有问题并且非常方便的(Anaconda是不是考虑给我发点钱?)

  • Numpy,版本1.14.3

    NumPy是Python语言的一个扩充程式库。支援高阶大量的维度阵列与矩阵运算,此外也针对阵列运算提供大量的数学函数函式库。NumPy的前身Numeric最早是由Jim Hugunin与其它协作者共同开发,2005年,Travis Oliphant在Numeric中结合了另一个同性质的程式库Numarray的特色,并加入了其它扩充功能而开发了NumPy。NumPy为开放原始码并且由许多协作者共同维护开发。

注:介绍引用自维基百科,本人不对从其引用的内容的准确性作任何保证,侵删,下同。

  • Matplotlib,版本2.2.2

    matplotlib 是Python编程语言及其数值数学扩展包 NumPy的可视化操作界面。它为利用通用的图形用户界面工具包,如Tkinter, wxPython, Qt或GTK+向应用程序嵌入式绘图提供了应用程序接口(API)。此外,matplotlib还有一个基于图像处理库(如开放图形库OpenGL)的pylab接口,其设计与MATLAB非常类似–尽管并不怎么好用。SciPy就是用matplotlib进行图形绘制。matplotlib最初由John D. Hunter撰写,它拥有一个活跃的开发社区,并且根据BSD样式许可证分发。 在John D. Hunter2012年去世前不久,迈克尔Droettboom被提名为matplotlib的主要开发者。截至到2015年10月30日,matplotlib 1.5.x支持Python 2.7到3.5版本。 Matplotlib 1.2是matplotlib的第一个版本,支持Python 3.x. Matplotlib 1.4是matplotlib支持Python 2.6的最后一个版本。

环境安装

Python (Anaconda)

Anaconda 是一种Python语言的免费增值开源发行版,用于进行大规模数据处理, 预测分析, 和科学计算, 致力于简化包的管理和部署。 Anaconda使用软件包管理系统Conda进行包管理。

Anaconda 的使用非常简单,只需要安装后就可以使用对应的环境。直接从https://www.anaconda.com下载需要的发行版,使用GUI安装即可。本文及其系列均采用Anaconda 3 x64作为默认的Python环境。


Tensorflow

TensorFlow是一个开源软件库,用于各种感知和语言理解任务的机器学习。目前被50个团队用于研究和生产许多Google商业产品,如语音识别、Gmail、Google 相册和搜索,其中许多产品曾使用过其前任软件DistBelief。TensorFlow最初由Google Brain团队开发,用于Google的研究和生产,于2015年11月9日在Apache 2.0开源许可证下发布。

由于我的训练用计算机使用了nVidia GTX 1080 8GB显卡,所以可以采用使用GPU进行计算的Tensorflow框架,使用GPU的好处是速度会快很多(根据我以往经验判断大致可以比CPU版本快两个数量级)。

1. 安装CUDA (限GPU版)

使用GPU版的Tensorflow需要计算机安装有一张以上的nVidia的GPU,并且Compute Capability不低于3.0(可以通过https://developer.nvidia.com/cuda-gpus查看GPU是否符合要求)。

可以看到我的GTX1080的Compute Capability 是6.1,可以使用CUDA和GPU版Tensorflow。

然后安装CUDA9.0。从https://developer.nvidia.com/cuda-90-download-archive下载CUDA 9.0(9.1版本不太确定能否兼容)。安装后在命令行输入

nvcc -V

(注意-V要大写)检查是否安装了CUDA。

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2017 NVIDIA Corporation
Built on Fri_Nov__3_21:08:12_Central_Daylight_Time_2017
Cuda compilation tools, release 9.1, V9.1.85

根据这个提示信息可以知道我这里已经安装好了9.1.85的CUDA(我的电脑上9.0和9.1是共存的),所以CUDA已经安装完成了。

2. 安装cuDNN(仅限GPU版)

安装完CUDA后还需要安装cuDNN。

访问https://developer.nvidia.com/cudnn后注册nVidia Developer帐号,然后下载对应CUDA版本的cuDNN,将下载解压后的cuda文件夹中的内容放到

X:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\版本号,X是CUDA的安装分区

对应文件夹下即可。

3. 安装Tensorflow(使用pip)

因为安装了Anaconda,所以这一步就很简单了。

打开Anaconda命令行(在开始菜单Anaconda文件夹下),输入:

pip install tensorflow-gpu

即可开始安装。

如果提示更新pip,请根据提示进行操作。在安装过程中有可能需要为pip指定--ignore-installed参数

安装完成后,在命令行下输入

python

进入python环境,然后执行下面的代码测试tensorflow的安装情况:

import tensorflow as tf
hello = tf.constant('hello, world')
sess = tf.Session()
sess.run(hello)
sess.close()

如果你看到类似的输出:

2018-05-08 03:34:12.038568: I T:\src\github\tensorflow\tensorflow\core\platform\cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2
2018-05-08 03:34:12.495307: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:1356] Found device 0 with properties:
name: GeForce GTX 1080 major: 6 minor: 1 memoryClockRate(GHz): 1.7335
pciBusID: 0000:06:00.0
totalMemory: 8.00GiB freeMemory: 6.60GiB
2018-05-08 03:34:12.508107: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:1435] Adding visible gpu devices: 0
2018-05-08 03:34:14.156435: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:923] Device interconnect StreamExecutor with strength 1 edge matrix:
2018-05-08 03:34:14.164752: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:929]      0
2018-05-08 03:34:14.168988: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:942] 0:   N
2018-05-08 03:34:14.173767: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:1053] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 6379 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1080, pci bus id: 0000:06:00.0, compute capability: 6.1)

和:

b'hello'

那么Tensorflow就已经安装完毕了。


OpenCV

起初我打算直接使用Anaconda安装OpenCV,但是没有找到很好的方法,因为Anaconda3默认会安装OpenCV3。所以使用了下面的方法:

访问https://opencv.org/releases.html,下载2.4.13.6下的Win pack,解压后找到cv2.pyd,解压到Anaconda安装目录的

Anaconda安装目录\Lib\site-packages

下,然后进入Anaconda命令行并进入python环境,输入

import cv2

如果没有报错,则OpenCV安装成功。