「数据挖掘与应用」实验项目之RotateImage旋转图片插图

问题描述

在如今的信息化时代,图片已经成为人们获取信息的重要途径,但由于图像的倾斜,会给数据挖掘和识别带来一定的困难和误差,因此如何解决图像倾斜问题成为了一个热点话题。

问题描述

在实际应用中,很多图像都存在不同程度的倾斜,这会导致图像识别的准确率受到影响。为了解决这个问题,我们需要对图像进行旋转纠偏,使其变得水平,以提高后续数据挖掘和识别的精度。

具体而言,需要完成以下任务:

  1. 图像自动检测:对于给定的图像,首先需要通过算法自动检测出其中的文字或其他特征,并确定其位置和大小。
  2. 图像预处理:对于检测出的特征,需要进行一些预处理,比如灰度化、二值化等操作,以消除扫描畸变和光照条件影响,提取出待处理图像中的文本、图形等特征。
  3. 直线检测和角度计算:利用霍夫变换或其他方法检测图像中的倾斜直线,并计算出其倾斜角度。
  4. 图像旋转处理:通过对图像进行逆时针旋转该角度的操作,可以实现真正的图像纠偏,从而达到更高的识别精度。
  5. 验证码识别:除了图像校正之外,RotateImage还提供了验证码识别功能,可帮助识别各种常见的验证码,例如数字、字母、汉字等等。

解决方案

为了解决图像倾斜问题,我们设计了一个旨在解决该问题的数据挖掘项目——RotateImage。

RotateImage采用Python语言编写,基于多个开源库(如OpenCV、Pillow等),实现了对图片的自动检测并纠正,可以高效地将图像进行纠偏处理,并且对识别验证码等相关任务具有很好的支持。它具有以下几个特点:

  1. 图像预处理:RotateImage内置灰度化、二值化等操作,能够消除扫描畸变和光照条件影响,并提取出待处理图像中的文本、图形等特征。
  2. 直线检测和角度计算:RotateImage利用霍夫变换或其他方法检测图像中的倾斜直线,并计算出其倾斜角度,从而实现旋转校正。
  3. 快速、准确的图像纠偏:通过对图像进行逆时针旋转该角度的操作,RotateImage可以实现真正的图像纠偏,达到更高的识别精度。
  4. 验证码识别:除了图像校正之外,RotateImage还提供了验证码识别功能,可帮助识别各种常见的验证码,例如数字、字母、汉字等等,是一个非常实用的数据挖掘工具。

应用场景

RotateImage可以应用于很多领域,比如:

  1. 银行卡、身份证等证件扫描后的信息识别。
  2. 电商网站注册时的验证码识别。
  3. 自动化车牌识别等。

解决方案概述

当今,倾斜图像处理是许多领域所需的一个重要问题。为了提高数据挖掘和识别的精度,在处理倾斜图像时需要进行旋转纠偏等操作。

本项目的解决方案主要包括以下几个步骤:

图像预处理

在检测出待处理的图像时,我们首先需要对其进行预处理,包括灰度化、二值化、去噪等步骤来提取特征信息。这有助于消除扫描畸变和光照条件影响,提高后续处理的准确性。

检测直线和计算角度

通过霍夫变换等算法,可以检测出图像中的倾斜直线,并计算出其角度。这一步骤可以帮助我们知道图像的倾斜程度,进而进行旋转纠偏等操作。

图像旋转

在得到直线角度后,我们可以进行逆时针旋转该角度的操作,以实现真正的图像纠偏。这一步骤可以提高图像的识别精度和效率,从而满足不同领域的要求。

设计算法描述

预处理验证码图像

对于待处理的验证码图像,首先需要进行预处理,以提取图像中的重要特征信息,方便后续的处理和识别。常见的预处理方法包括灰度化、二值化、降噪等操作。

获取旋转角度

旋转角度是解决验证码倾斜问题的关键因素。我们可以使用图像特征的直线检测和直线拟合来获取旋转角度。具体而言,可以采用霍夫变换或其他方法,在图像中检测出直线的位置及其对应的角度。然后进行直线拟合,得到最佳拟合直线,并计算该直线与原始水平线之间的夹角,即为图像的旋转角度。

旋转纠偏

在获取到旋转角度之后,需要对图像进行旋转纠偏操作,使其回归到正常状态。这里可以结合您引入的深度学习模型,利用预测出来的旋转角度,通过相应的图像旋转算法对图像进行旋转纠偏操作。

分割识别

旋转纠偏后的验证码图像可以进一步进行分割,分解成单个字符或数字,以便进行自动识别。常见的分割方法包括基于像素点和基于轮廓。对于每一个字符或数字,可以使用您提供的深度学习模型进行相应的分类和识别操作。

算法优化

最后,根据实际应用场景和需求,针对不同的验证码类型和难度程度,对算法进行适当的优化调整。例如,可以增加数据增强、改变图像大小、增加网络层数、调整超参数等操作,以提高准确率和泛化能力。 

算法实现

import base64
import binascii
import json
import math
import os
import time

import cv2
import keras.backend as K
import numpy as np
import requests
from keras.applications.imagenet_utils import preprocess_input
from keras.models import load_model
from keras.optimizers import SGD


class RotateCaptcha():
    def __init__(self):
        # 加载模型
        model_location = os.path.join('.', 'custom_modules', 'rotnet_street_view_resnet50_keras2.hdf5')
        self.model = load_model(model_location, custom_objects={'angle_error': self.calculate_angle_error})

        # 随机梯度下降法 学习率为0.01,动量为0.9
        self.model.compile(loss='categorical_crossentropy',
                           optimizer=SGD(lr=0.01, momentum=0.9),
                           metrics=[self.calculate_angle_error])

        # 图像长宽尺寸
        self.size = (224, 224)

        # 下载图片使用的ua
        self.headers = {
            'Connection': 'keep-alive',
            'Pragma': 'no-cache',
            'Cache-Control': 'no-cache',
            'sec-ch-ua': '"Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"',
            'DNT': '1',
            'sec-ch-ua-mobile': '?0',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36',
            'Accept': '*/*',
            'Sec-Fetch-Site': 'same-site',
            'Sec-Fetch-Mode': 'no-cors',
            'Sec-Fetch-Dest': 'script',
            'Referer': 'https://www.baidu.com/s?rsv_idx=1&wd=31%E7%9C%81%E6%96%B0%E5%A2%9E%E7%A1%AE%E8%AF%8A13%E4%BE%8B+%E5%9D%87%E4%B8%BA%E5%A2%83%E5%A4%96%E8%BE%93%E5%85%A5&fenlei=256&ie=utf-8&rsv_cq=np.random.choice+%E4%B8%8D%E9%87%8D%E5%A4%8D&rsv_dl=0_right_fyb_pchot_20811_01&rsv_pq=c0b53cdc0005af92&oq=np.random.choice+%E4%B8%8D%E9%87%8D%E5%A4%8D&rsv_t=2452p17G6e88Hpj%2FkNppuwT%2FFjr8KeLJKT4KqqeSLqr7MhD7HbIYjtM9KVc&rsf=84b938b812815a59afcce7cc4e641b1d_1_15_8&rqid=c0b53cdc0005af92',
            'Accept-Language': 'zh-CN,zh;q=0.9',
        }

    def showImg(self, incorrect_image, correct_image):
        '''
        展示图片
        '''
        cv2.imshow('Incorrect Image', incorrect_image)
        cv2.imshow('Correct Image', correct_image)
        cv2.waitKey(0)

    def getImgFromDisk(self, imgPath):
        image = cv2.imread(imgPath)
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        return image

    def getImgFromUrl(self, url):
        '''
        通过url获取图片,获取的图片有可能有部分打码,不过模型能正常识别。 如果想防止水印,可以调整cookie和header
        '''
        r = requests.get(url, headers=self.headers)

        image = cv2.imdecode(np.frombuffer(r.content, np.uint8), cv2.IMREAD_COLOR)  # 直接解码网络数据
        self.showImg(image)
        return image

    def getImgFromBase64(self, img_base64_string):
        # 将 base64 编码的字符串解码成二进制格式
        img_bytes = base64.b64decode(img_base64_string)
        image = cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR)  # 直接解码网络数据
        return image

    def predictAngle(self, image):
        diameter = image.shape[0]  # 直径
        side_length = math.floor((diameter / 2) * 1.414)  # 圆内正方形最大边长
        cropped = math.floor((diameter - side_length) / 2)
        image = image[cropped:cropped + side_length, cropped:cropped + side_length]
        image = cv2.resize(image, self.size)

        image = np.expand_dims(image, axis=0)

        x = preprocess_input(image)
        y_pred = np.argmax(self.model.predict(x), axis=1)

        return y_pred[0]

    def rotate(self, image, angle):
        # 获取图像大小
        # 不,那不是错误——NumPy 以相反的方式存储图像矩阵
        image_size = (image.shape[1], image.shape[0])
        image_center = tuple(np.array(image_size) / 2)

        # 将 OpenCV 3x2 旋转矩阵转换为 3x3 矩阵
        rotation_matrix = np.vstack(
            [cv2.getRotationMatrix2D(image_center, angle, 1.0), [0, 0, 1]]
        )

        rotation_matrix_no_translation = np.matrix(rotation_matrix[0:2, 0:2])

        # 下面的计算用到了简写
        image_w2 = image_size[0] * 0.5
        image_h2 = image_size[1] * 0.5

        # 获取图像角的旋转坐标
        rotated_coords = [
            (np.array([-image_w2, image_h2]) * rotation_matrix_no_translation).A[0],
            (np.array([image_w2, image_h2]) * rotation_matrix_no_translation).A[0],
            (np.array([-image_w2, -image_h2]) * rotation_matrix_no_translation).A[0],
            (np.array([image_w2, -image_h2]) * rotation_matrix_no_translation).A[0]
        ]

        # 获取新图像的大小
        x_coords = [pt[0] for pt in rotated_coords]
        x_pos = [x for x in x_coords if x > 0]
        x_neg = [x for x in x_coords if x < 0]

        y_coords = [pt[1] for pt in rotated_coords]
        y_pos = [y for y in y_coords if y > 0]
        y_neg = [y for y in y_coords if y < 0]

        right_bound = max(x_pos)
        left_bound = min(x_neg)
        top_bound = max(y_pos)
        bot_bound = min(y_neg)

        new_w = int(abs(right_bound - left_bound))
        new_h = int(abs(top_bound - bot_bound))

        # 我们需要一个平移矩阵来保持图像居中
        translation_matrix = np.matrix([
            [1, 0, int(new_w * 0.5 - image_w2)],
            [0, 1, int(new_h * 0.5 - image_h2)],
            [0, 0, 1]
        ])

        # 计算旋转和平移组合的变换
        affine_matrix = (np.matrix(translation_matrix) * np.matrix(rotation_matrix))[0:2, :]

        # 应用变换
        result = cv2.warpAffine(
            image,
            affine_matrix,
            (new_w, new_h),
            flags=cv2.INTER_LINEAR
        )

        result[result == 0] = 255

        return result

    def calculate_angle_difference(self, x, y):
        # 计算两个角度之间的最小差值。
        return 180 - abs(abs(x - y) - 180)

    def calculate_angle_error(self, y_true, y_pred):
        # 计算真实角度和预测角度之间的平均差异。
        # 每个角度表示为一个二进制向量。
        # 计算真实角度和预测角度之间的最小差值
        diff = self.calculate_angle_difference(K.argmax(y_true), K.argmax(y_pred))
        # 计算差值的平均值
        return K.mean(K.cast(K.abs(diff), K.floatx()))


# 云函数部署 本地注释
def main_handler(event, context):
    st = time.time()
    try:
        img_string = event['body']
        img_string = json.loads(img_string)['img_base64']
    except (KeyError, ValueError) as e:
        print('无法获取图片数据:{}'.format(e))
        return {
            'statusCode': 400,
            'success': False,
            'msg': '无法获取图片数据,请检查参数是否正确'
        }

    try:
        img_string = img_string.split(',')[1]
        img_data = base64.b64decode(img_string)
    except (IndexError, binascii.Error) as e:
        print(f"img_base64 参数格式错误!{str(e)}")
        return {
            'statusCode': 400,
            'success': False,
            'msg': 'img_string 参数格式错误'
        }
    rotateCaptcha = RotateCaptcha()
    rotated_image = rotateCaptcha.getImgFromBase64(img_string)

    predicted_angle = rotateCaptcha.predictAngle(rotated_image)  # 预测还原角度
    print("预测角度:{}".format(predicted_angle))
    print("总耗时: ", time.time() - st, "秒")
    corrected_image = rotateCaptcha.rotate(rotated_image, -predicted_angle)  # 矫正后图像
    rotateCaptcha.showImg(corrected_image)  # 展示图像

    return {
        'statusCode': 200,
        'success': True,
        'angle': predicted_angle,
        'total_consumption': time.time() - st,
    }


def get_image_files(path):
    image_extensions = ['.jpg', '.jpeg', '.png', '.gif']  # 图片文件扩展名列表
    image_files = []  # 用于保存获取到的图片文件路径

    for root, dirs, files in os.walk(path):
        for file in files:
            ext = os.path.splitext(file)[-1].lower()  # 获取文件扩展名
            if ext in image_extensions:
                image_files.append(os.path.join(root, file))  # 将找到的图片文件路径加入结果列表

    return image_files


if __name__ == '__main__':
    rotateCaptcha = RotateCaptcha()

    # 从 train-data 获取图片
    rotated_image = rotateCaptcha.getImgFromDisk('train/train-data/1615096412.jpg')

    predicted_angle = rotateCaptcha.predictAngle(rotated_image)  # 预测还原角度
    print("需旋转角度:{}".format(predicted_angle))

    corrected_image = rotateCaptcha.rotate(rotated_image, -predicted_angle)  # 矫正后图像

    rotateCaptcha.showImg(rotated_image, corrected_image)  # 展示二者图像

实验结果测试与分析

实验测试

我们首先去手动触发百度反爬虫机制,下载一张用于人机验证的旋转图片,例如以下,

「数据挖掘与应用」实验项目之RotateImage旋转图片插图1

下载完成后,放入项目的 train-data 文件夹内,随后读入这张图片,进行旋转测试。

由于图片读入后我们需要将它转换为np数组,为了降低对于模型的难度,我们最后旋转出来的图片可能有些许变色,但是不会影响到最终结果。

例如以下:

「数据挖掘与应用」实验项目之RotateImage旋转图片插图2

左侧为未纠正的图片,右侧为已经纠正的图片,实验结果成功,并输出需要旋转的角度值。

「数据挖掘与应用」实验项目之RotateImage旋转图片插图3

思考及学习心得

旋转验证码识别的难点

旋转验证码是一种常见的验证码类型,其特点是字母或数字文字出现在一个旋转角度不定的背景图案中。由于旋转角度的不确定性,使得识别旋转验证码成为一项具有挑战性的难点任务。在完成RotateImage项目的过程中,我逐渐认识到了这个问题的复杂性并尝试寻找解决方案与优化方法。

深度学习模型的应用

在项目中,我探索使用深度学习模型来解决旋转验证码识别问题。通过针对数据集进行适当的预处理和增强,我搭建了一个基于卷积神经网络(CNN)的旋转验证码识别模型,并进行了训练和优化。通过不断地调整模型的结构和超参数,我最终获得了一个较高的准确率,并将其应用于实际的验证码识别场景中。

预处理和分割技术的应用

除了深度学习模型外,我还通过应用图像预处理和分割技术,对旋转验证码图像进行了优化处理。通过对图像进行灰度化、二值化、降噪、旋转纠偏和分割等操作,我成功地提取出了图像中的关键信息,使得识别模型得以更加准确地预测验证码的值。

项目实践的意义

通过完成RotateImage项目,我对计算机视觉领域的知识和技术有了更深入的了解,并学会了如何构建和优化复杂的机器学习模型。此外,我还加强了对数据结构和算法的理解和掌握,完善了我的编程技能。这些能力的提升不仅对于日后从事机器学习和计算机视觉相关工作具有重要的价值,而且也丰富了我的个人履历和技能背景,为我今后的职业发展奠定了坚实的基础。

后续

由于该项目具有非常高的商业价值,所以我后续会选择将该验证接口搭建在我的小型服务器上,后续可以通过API请求它,打破收费封锁!