Python图像处理工具类,总有一个适合你!

Python关于图像处理的模块包特别多,可参见此链接:[Python中的十大图像处理工具]。

目前用的比较多的还是opencv-python、numpy和PIL。

本文就这三个库封装了一些常用的工具类(以opencv-python为主),

功能包括:

1.图像拼接
2.图像旋转
3.图像裁剪
4.图像批量命名
5.在图像中添加中文
6.在图像中绘制线条(绊线)
7.图像亮度和对比度调节
8.图像光照补偿
9.视频转图像
10.视频片段截取
11.视频连接
12.利用背景减法获取矩形框(用于视频中动态目标的位置检测)
13.利用帧间差分法获取矩形框(用于视频中动态目标的位置检测)
14.图像的轮廓提取(Sobel与Canny算子)
15.目录中的图像转成视频文件

代码如下所示:

# -*- coding:utf-8 -*-
# 图像本质上是包含数据点像素的标准Numpy数组
import os
import time

import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont


def imageJoint(img1Path, img2Path, axis=1, isZoomBoolean=False, zoomScale=2):
    '''
    图像拼接
    :param img1Path: 图像1的路径
    :param img2Path: 图像2的路径
    :param axis: 连接方式,可选值为0和1,0表示上下连接,1表示左右连接,默认为1
    :param isZoomBoolean: 是否进行图像缩放(长宽各缩小一倍)
    :param zoomScale: 图像缩放比例,默认长宽各缩小1倍
    :return:
    '''
    img1 = cv2.imread(img1Path)
    img2 = cv2.imread(img2Path)
    if isZoomBoolean is True:
        hh, ww, channel = img1.shape
        img1 = cv2.resize(img1, (ww // zoomScale, hh // zoomScale))
        img2 = cv2.resize(img2, (ww // zoomScale, hh // zoomScale))
    # image = np.hstack([img1, img2])  # 水平拼接
    # image = np.vstack([img1, img2])  # 垂直拼接
    image_joint = np.concatenate([img1, img2], axis=axis)  # axis=0时为垂直拼接;axis=1时为水平拼接
    cv2.imwrite("image_joint.jpg", image_joint)


def imageRotate(imagePath, angle):
    '''
     图像旋转
     :param img2Path: 图像路径
     :param angle: 旋转角度
    '''
    image = cv2.imread(imagePath)
    (h, w) = image.shape[:2]  # 返回(高,宽,色彩通道数),此处取前两个值返回
    # 抓取旋转矩阵(应用角度的负值顺时针旋转)。参数1为旋转中心点;参数2为旋转角度,正值表示逆时针旋转;参数3为各向同性的比例因子
    M = cv2.getRotationMatrix2D((w / 2, h / 2), -angle, 1.0)
    # 计算图像的新边界维数
    newW = int((h * np.abs(M[0, 1])) + (w * np.abs(M[0, 0])))
    newH = int((h * np.abs(M[0, 0])) + (w * np.abs(M[0, 1])))
    # newW = int(h * fabs(sin(radians(angle))) + w * fabs(cos(radians(angle))))
    # newH = int(w * fabs(sin(radians(angle))) + h * fabs(cos(radians(angle))))
    # 调整旋转矩阵以考虑平移
    M[0, 2] += (newW - w) / 2
    M[1, 2] += (newH - h) / 2
    # 执行实际的旋转并返回图像
    img_rotate = cv2.warpAffine(image, M, (newW, newH))  # borderValue 缺省,默认是黑色
    cv2.imwrite('./rotateImage.jpg', img_rotate)


def imageCut(imagePath):
    '''
    图像裁剪
    :param imagePath: 图像路径
    :return:
    '''
    img = cv2.imread(imagePath)
    # 在图像中绘制ROI矩形框
    fold_box = cv2.selectROI('Cut image, press the Space bar or Enter to finish!', img, False)
    # 以ROI矩形框的左顶点为起始点,绘制对角直线
    startPoint = (fold_box[0], fold_box[1])  # 裁剪的左上角像素点
    endPoint = (fold_box[0] + fold_box[2], fold_box[1] + fold_box[3])  # 裁剪的右下角像素点
    cutImg = img[startPoint[1]:endPoint[1], startPoint[0]:endPoint[0]]
    cv2.imwrite("cutImage.jpg", cutImg)


def imageCut2(imagePath, startPoint=(0, 0), endPoint=(0, 0)):
    '''
    图像裁剪
    :param imagePath: 图像路径
    :param startPoint: 裁剪的左上角像素坐标
    :param endPoint: 裁剪的右下角像素坐标
    :return:
    '''
    img = cv2.imread(imagePath)
    cutImg = img[startPoint[1]:endPoint[1], startPoint[0]:endPoint[0]]
    cv2.imwrite("cutImage.jpg", cutImg)


def renameImageBatch(imgPath, newPath, startNumber=0):
    '''
    传入图片所在目录,将目录中的图片以纯数字形式批量重命名,并以jpg格式保存
    :param imgPath: 图片所在路径
    :param newPath: 命名后的图片保存的新路径
    :param startNumber: 起始数字
    :return:
    '''
    for imgName in os.listdir(imgPath):
        print(imgName)
        imageFullPath = imgPath + imgName
        startNumber += 1
        img = cv2.imread(imageFullPath)
        cv2.imwrite(newPath + str(startNumber) + ".jpg", img)
    cv2.destroyAllWindows()


def addChineseToImage(img, text, textPoint=(0, 0), textColor=(255, 0, 0)):
    '''
    传入opencv读取后的图像,在图像中添加中文
    :param img: 需要添加中文的图像
    :param text: 需要添加的中文文本
    :param textPoint: 文本位置的像素坐标,此处默认图像左上角(0,0)处
    :param textColor: 文本颜色,此处默认为红色
    :return: 返回cv2形式的目标图像
    '''
    # cv2转PIL(cv2和PIL中颜色的hex码的储存顺序不同)
    img_cv2 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img_pil = Image.fromarray(img_cv2)
    # PIL图片上打印汉字
    draw = ImageDraw.Draw(img_pil)
    # 参数1:字体文件路径,simhei为黑体(Windows系统自带);参数2:字体大小,默认10
    font = ImageFont.truetype("simhei.ttf", 20, encoding="utf-8")
    # font = ImageFont.truetype('NotoSerifCJK-Regular.ttc', 20)
    # 参数1:文本像素坐标;参数2:打印文本;参数3:字体颜色;参数4:字体
    draw.text(textPoint, text, textColor, font=font)
    # PIL图片转cv2 图片
    img_target = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
    return img_target


def foldLineToImage(imagePath, isVertical=False, isHorizontal=False, lineColor=(255, 0, 0),
                    lineSize=3):
    '''
    在图像中绘制线条
    :param imagePath: 图像原始路径
    :param isVertical: 是否绘制垂直线条,默认False
    :param isHorizontal: 是否绘制水平线条,默认False
    :param lineColor: 线条颜色
    :param lineSize: 线条粗细
    :return:
    '''
    image = cv2.imread(imagePath)
    # 在图像中绘制ROI矩形框
    fold_box = cv2.selectROI('Draw line, press the Space bar or Enter to finish!', image, False)
    # 以ROI矩形框的左顶点为起始点,绘制对角直线
    startPoint = (fold_box[0], fold_box[1])  # 直线起始点
    endPoint = (fold_box[0] + fold_box[2], fold_box[1] + fold_box[3])  # 直线结束点
    if isVertical is True:
        cpoint = (startPoint[0] + endPoint[0]) // 2
        startPoint = (cpoint, startPoint[1])
        endPoint = (cpoint, endPoint[1])
    if isHorizontal is True:
        cpoint = (startPoint[1] + endPoint[1]) // 2
        startPoint = (startPoint[0], cpoint)
        endPoint = (endPoint[0], cpoint)
    print(startPoint, endPoint)
    cv2.line(image, startPoint, endPoint, lineColor, lineSize)
    cv2.imwrite('foldLine.jpg', image)


def adjustLightAndContrast(imagePath, ALPHA=1, BETA=20):
    '''
    图像亮度和对比度调节
    :param imagePath: 图像路径
    :param ALPHA:
    :param BETA:
    :return:
    '''
    img = Image.open(imagePath)
    c, r = img.size
    arr = np.array(img)
    for i in range(r):
        for j in range(c):
            for k in range(3):
                temp = arr[i][j][k] * ALPHA + BETA
                if temp > 255:
                    arr[i][j][k] = 2 * 255 - temp
                else:
                    arr[i][j][k] = temp
    return Image.fromarray(arr)


def lightCompensation(imagePath):
    '''
    光照补偿
    :param imagePath: 图像路径
    :return:
    '''
    img = cv2.imread(imagePath)
    img = img.transpose(2, 0, 1).astype(np.uint32)
    avgB = np.average(img[0])
    avgG = np.average(img[1])
    avgR = np.average(img[2])

    avg = (avgB + avgG + avgR) / 3

    img[0] = np.minimum(img[0] * (avg / avgB), 255)
    img[1] = np.minimum(img[1] * (avg / avgG), 255)
    img[2] = np.minimum(img[2] * (avg / avgR), 255)
    image = img.transpose(1, 2, 0).astype(np.uint8)
    return image


def histEqualize(imagePath):
    '''
    光照补偿:直方图均衡化(把原始图的直方图变换为均匀分布的形式,增强图像整体对比度)
    :param imagePath: 图像路径
    :return:
    '''
    img = cv2.imread(imagePath)
    ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCR_CB)
    channels = cv2.split(ycrcb)
    cv2.equalizeHist(channels[0], channels[0])  # equalizeHist(in,out)
    cv2.merge(channels, ycrcb)
    img_eh = cv2.cvtColor(ycrcb, cv2.COLOR_YCR_CB2BGR)
    return img_eh


def videoToFrame(videoPath, imgPath, num=5):
    '''
    将单个视频文件处理成帧
    :param videoPath: 视频所在路径
    :param imgPath: 处理后的图像保存路径
    :param num: 间隔num帧数存储一张,默认5帧保留一张
    :return:
    '''
    camera = cv2.VideoCapture(videoPath)
    i = 0
    while camera.isOpened():
        i += 1
        timer = cv2.getTickCount()
        (ok, frame) = camera.read()
        fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer)
        if ok:
            if i % num == 1:
                cv2.imwrite(imgPath + str(int(i / num)) + ".jpg", frame)
            cv2.putText(frame, "FPS : " + str(int(fps)), (30, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.75,
                        (50, 170, 50), 2)
            cv2.imshow("Frame", frame)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
        else:
            camera.release()
    cv2.destroyAllWindows()


def videosToFrame(videosPath, imgPath, num=5):
    '''
    将目标路径下的(多个)视频全部处理成帧
    :param videosPath: 视频所在路径
    :param imgPath: 处理后的图像保存路径
    :param num: 间隔num帧数存储一张,默认5帧保留一张
    :return:
    '''
    for filename in os.listdir(videosPath):
        print('开始处理:' + filename)
        name = filename[0:3].lower()
        name = filename[:-4]
        camera = name + '_'
        print(camera)
        camera = cv2.VideoCapture(videosPath + filename)
        i = 0
        while camera.isOpened():
            i += 1
            timer = cv2.getTickCount()
            (ok, frame) = camera.read()
            fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer)
            if ok:
                if i % num == 1:
                    cv2.imwrite(imgPath + name + '_' + str(int(i / num)) + ".jpg", frame)
                cv2.putText(frame, "FPS : " + str(int(fps)), (30, 30), cv2.FONT_HERSHEY_SIMPLEX,
                            0.75, (50, 170, 50), 2)
                cv2.imshow("Frame", frame)
                if cv2.waitKey(1) & 0xFF == ord('q'):
                    break
            else:
                camera.release()

        cv2.destroyAllWindows()


def videoCut(videoPath):
    '''
    视频中间部分截取,按空格键(SPACE)开始保存,按q键结束保存
    :param videoPath: 视频所在路径
    :return:
    '''
    video = cv2.VideoCapture(videoPath)
    imageSize = (int(video.get(3)), int(video.get(4)))  # 视频尺寸
    VIDEO_FPS = int(video.get(cv2.CAP_PROP_FPS))  # 视频帧率
    print(imageSize)
    print(video.get(cv2.CAP_PROP_FPS))
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    videoWriter = cv2.VideoWriter('video.avi', fourcc, VIDEO_FPS, imageSize)
    flag_save = False
    while video.isOpened():
        (ok, frame) = video.read()
        cv2.imshow("Frame", frame)
        # 按空格键(SPACE)开始保存
        if cv2.waitKey(1) & 0xFF == ord(' '):
            flag_save = True
            print("Already started saving...")
        if flag_save is True:
            videoWriter.write(frame)
        # 按q键结束保存
        if cv2.waitKey(1) & 0xFF == ord('q'):
            print("Video saved successfully, has exited!")
            break

    video.release()
    cv2.destroyAllWindows()


def videoCut2(videoPath, startSecond, endSecond):
    '''
    视频中间部分截取
    :param videoPath: 视频所在路径
    :param startSecond: 开始时间,从视频的 startSecond 秒处开始保存
    :param endSecond: 结束时间,保存至 endSecond 秒结束,或者按键盘q键结束
    :return:
    '''
    video = cv2.VideoCapture(videoPath)
    imageSize = (int(video.get(3)), int(video.get(4)))  # 视频尺寸
    VIDEO_FPS = int(video.get(cv2.CAP_PROP_FPS))  # 视频帧率
    print(imageSize)
    print(video.get(cv2.CAP_PROP_FPS))
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    videoWriter = cv2.VideoWriter('video.avi', fourcc, VIDEO_FPS, imageSize)

    i = 0
    while video.isOpened():
        (ok, frame) = video.read()
        i += 1
        # 从视频的 startSecond 秒处开始保存
        if i < startSecond * VIDEO_FPS:
            continue
        elif i > startSecond * VIDEO_FPS:
            cv2.imshow("Frame", frame)
            videoWriter.write(frame)
        # 保存至 endSecond 秒结束
        if i > endSecond * VIDEO_FPS:
            break
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    video.release()
    cv2.destroyAllWindows()


def videoJoint(videoPath1, videoPath2, savePath, resizeVideo=0, axis=0):
    '''
    视频(水平/垂直)连接
    :param videoPath1: 视频1的路径
    :param videoPath2: 视频1的路径
    :param savePath: 视频保存路径,包括保存的视频名称,文件名以 .avi 结尾
    :param resizeVideo: 视频缩放比例(以视频1的像素为参照),0表示不缩放(默认值),正数表示放大,负数表示缩小,缩放倍数与resizeVideo的取值成正比
    :param axis: 0表示垂直连接(默认值),1表示水平连接
    :return: 
    '''
    cam1 = cv2.VideoCapture(videoPath1)
    cam2 = cv2.VideoCapture(videoPath2)
    if resizeVideo == 0:
        ww = int(cam1.get(3))
        hh = int(cam1.get(4))
    elif resizeVideo > 0:
        ww = int(cam1.get(3) * (resizeVideo + 1))
        hh = int(cam1.get(4) * (resizeVideo + 1))
    elif resizeVideo < 0:
        ww = int(cam1.get(3) // (1 - resizeVideo))
        hh = int(cam1.get(4) // (1 - resizeVideo))

    CAMERA_FPS = cam1.get(cv2.CAP_PROP_FPS)
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    if axis == 1:
        videoWriter = cv2.VideoWriter(savePath, fourcc, CAMERA_FPS, (ww * 2, hh))
    else:
        videoWriter = cv2.VideoWriter(savePath, fourcc, CAMERA_FPS, (ww, hh * 2))

    while True:
        (ok1, frame1) = cam1.read()
        (ok2, frame2) = cam2.read()
        if ok1 and ok2:
            frame1 = cv2.resize(frame1, (ww, hh))
            frame2 = cv2.resize(frame2, (ww, hh))
            # frame1 = addChineseToImage(frame1, "视频1", (5, 5))
            # cv2.putText(frame1, "Video one", (50, 50),cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), 2)
            image = np.concatenate([frame1, frame2], axis=axis)  # axis=0时为垂直拼接;axis=1时为水平拼接

            cv2.imshow("camera1", image)
            videoWriter.write(image)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
        else:
            break

    videoWriter.release()
    cam1.release()
    cam2.release()
    cv2.destroyAllWindows()


def detectTarget_BS(frame, minArea=1000, backgroundSubtractor=None, structuringElement=None):
    '''
    利用背景减法获取矩形框,用于视频中动态目标的位置检测
    :param frame: 待检测的视频帧
    :param areaThreshold: 检测矩形框的最小面积阀值,默认1000(单位:像素)
    :param backgroundSubtractor: 背景减除法算法对象
    :param structuringElement: 用于形态操作的指定大小和形状的结构元素
    :return:
    '''
    if backgroundSubtractor is None:
        backgroundSubtractor = cv2.createBackgroundSubtractorKNN(detectShadows=True)
    if structuringElement is None:
        structuringElement = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
    boxes = []
    gray_L = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    fgmask = backgroundSubtractor.apply(gray_L)  # 背景分割器,该函数计算了前景掩码
    # 二值化阈值处理,前景掩码含有前景的白色值以及阴影的灰色值
    # 在阈值化图像中,将像素差大于某一阈值(这里定为244)的标注为运动点,赋值为255(白色),其余点赋值为0(黑色)
    th = cv2.threshold(fgmask.copy(), 244, 255, cv2.THRESH_BINARY)[1]
    dilated = cv2.dilate(th, structuringElement, iterations=2)  # 形态学膨胀
    # 计算一幅图像中目标的轮廓
    try:
        img, contours, hierarchy = cv2.findContours(image=dilated, mode=cv2.RETR_EXTERNAL,
                                                    method=cv2.CHAIN_APPROX_SIMPLE)
    except ValueError:
        # print('ValueError: not enough values to unpack (expected 3, got 2), caught!')
        contours, hierarchy = cv2.findContours(image=dilated, mode=cv2.RETR_EXTERNAL,
                                               method=cv2.CHAIN_APPROX_SIMPLE)

    for cnt in contours:
        if cv2.contourArea(cnt) > minArea:
            (x, y, w, h) = cv2.boundingRect(cnt)  # 外接矩形
            boxes.append((x, y, w, h))

    return frame, boxes


def detectTarget_TD(imagePath1, imagePath2, minArea=500, thresh=40):
    '''
    利用帧间差分法获取矩形框,用于视频中动态目标的位置检测
    :param imagePath1: 图像1的路径
    :param imagePath2: 图像2的路径
    :param minArea: 运动目标的最小面积阈值
    :param thresh: 二值化处理的阈值,像素差大于该阀值的标注为运动点
    :return: 
    '''
    boxes = []
    previousframe = cv2.imread(imagePath1)
    currentframe = cv2.imread(imagePath2)
    image_joint = np.concatenate([previousframe, currentframe], axis=1)

    previousGray = cv2.cvtColor(previousframe, cv2.COLOR_BGR2GRAY)
    currentGray = cv2.cvtColor(currentframe, cv2.COLOR_BGR2GRAY)

    resultframe = cv2.absdiff(currentGray, previousGray)
    resultframe = cv2.medianBlur(resultframe, 3)

    ret, threshold_frame = cv2.threshold(resultframe, thresh, 255, cv2.THRESH_BINARY)
    thresh, contours, hierarchy = cv2.findContours(threshold_frame, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    print(len(contours))
    for cnt in contours:
        if cv2.contourArea(cnt) > minArea:
            (x, y, w, h) = cv2.boundingRect(cnt)  # 外接矩形
            boxes.append((x, y, w, h))
            cv2.rectangle(currentframe, (x, y), (x + w, y + h), (255, 0, 0), 3)

    return image_joint, resultframe, currentframe, boxes



def drawContour_Sobel(imagePath, ksize=5, sigmaX=1.5):
    '''
    用Sobel算子进行图像的轮廓提取
    :param imagePath: 图像路径
    :param ksize: 高斯波滤中高斯核的大小,必须为正奇数或0
    :param sigmaX: 高斯波滤中X方向上的高斯核标准差
    :return:
    '''
    image = cv2.imread(imagePath)
    frame_blurred = cv2.GaussianBlur(image, (ksize, ksize), sigmaX)
    sobelx = cv2.Sobel(frame_blurred, cv2.CV_64F, 1, 0)
    sobely = cv2.Sobel(frame_blurred, cv2.CV_64F, 0, 1)
    sobelx = np.uint8(np.absolute(sobelx))
    sobely = np.uint8(np.absolute(sobely))
    sobelcombine = cv2.bitwise_or(sobelx, sobely)
    cv2.imwrite('drawContour_Sobel.jpg', sobelcombine)
    # return sobelcombine


def drawContour_Canny(imagePath, threshold1=50, threshold2=180):
    '''
    用Canny算子进行图像的轮廓提取
    :param imagePath: 图像路径
    :param threshold1: Canny算法中的低阀值(迟滞过程的第一个阈值),取值范围:0~100
    :param threshold2: Canny算法中的高阀值(迟滞过程的第二个阈值),取值大于100
    :return:
    '''
    image = cv2.imread(imagePath)
    image = cv2.GaussianBlur(image, (5, 5), 1.5)
    canny = cv2.Canny(image, threshold1=threshold1, threshold2=threshold2)
    # 形态学:边缘检测
    _, Thr_img = cv2.threshold(canny, 210, 255, cv2.THRESH_BINARY)  # 设定红色通道阈值210(阈值影响梯度运算效果)
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))  # 定义矩形结构元素
    gradient = cv2.morphologyEx(Thr_img, cv2.MORPH_GRADIENT, kernel)  # 梯度
    cv2.imwrite('drawContour_Canny.jpg', gradient)
    # return gradient


def imageToVideo(imageDir, Frequency=3, imageWidth=640, imageHeight=480):
    '''
    目录下的图片转成视频文件
    :param imageDir: 图片所在目录
    :param Frequency: 写入频率(每秒多少帧,此处默认3)
    :param imageWidth: 图像指定宽度,默认640
    :param imageHeight: 图像指定高度,默认480
    :return:
    '''
    if os.path.exists(imageDir) is False:
        print('The path given does not exist, please check!')
        return
    imageList = os.listdir(imageDir)  # 获取该目录下的所有文件名
    if len(imageList) < 2:
        print('Too few pictures, exit save!')
        return

    video_name = "imageToVideo.avi"  # 导出路径
    fourcc = cv2.VideoWriter_fourcc('I', '4', '2', '0')
    vw = cv2.VideoWriter(video_name, fourcc, Frequency, (imageWidth, imageHeight))
    imgNum = 0
    for imageName in imageList:
        imageFullPath = imageDir + '/' + imageName
        if os.path.isdir(imageFullPath):
            continue
        print(imageFullPath)
        imgNum += 1
        img = cv2.imread(imageFullPath)
        img = cv2.resize(img, (imageWidth, imageHeight))
        cv2.imshow('', img)
        vw.write(img)  # 把图片写进视频
        time.sleep(0.3)
    print('Video write successful! There are {0} pictures!'.format(imgNum))
    vw.release()  # 释放


if __name__ == '__main__':
    pass

其他功能待完善!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值