滑塊驗證碼是一種常見的驗證碼形式,通過拖動滑塊與背景圖像中的缺口進行匹配,驗證用戶是否為真人。本文將詳細介紹基于圖像處理的滑塊驗證碼匹配技術,并提供優化代碼以提高滑塊位置偏移量的準確度,尤其是在背景圖滑塊陰影較淺的情況下。
一、背景知識
1.1 圖像處理概述
圖像處理是指對圖像進行分析和操作,以達到增強圖像、提取特征、識別模式等目的。常用的圖像處理技術包括高斯模糊、Canny 邊緣檢測、輪廓提取等。
1.2 滑塊驗證碼的原理
滑塊驗證碼通過用戶拖動滑塊,使滑塊圖像與背景圖像中的缺口對齊,從而驗證用戶的操作。實現滑塊驗證碼匹配的關鍵在于精確檢測背景圖像中缺口的位置。
二、技術實現
2.1 代碼實現
import base64
import os
from datetime import datetime
from typing import Union, Optionalimport cv2
import numpy as npclass SliderCaptchaMatch:def __init__(self,gaussian_blur_kernel_size=(5, 5),gaussian_blur_sigma_x=0,canny_threshold1=200,canny_threshold2=450,save_images=False,output_path=""):"""初始化SlideMatch類:param gaussian_blur_kernel_size: 高斯模糊核大小,默認(5, 5):param gaussian_blur_sigma_x: 高斯模糊SigmaX,默認0:param canny_threshold1: Canny邊緣檢測閾值1,默認200:param canny_threshold2: Canny邊緣檢測閾值2,默認450:param save_images: 是否保存過程圖片,默認False:param output_path: 生成圖片保存路徑,默認當前目錄"""self.GAUSSIAN_BLUR_KERNEL_SIZE = gaussian_blur_kernel_sizeself.GAUSSIAN_BLUR_SIGMA_X = gaussian_blur_sigma_xself.CANNY_THRESHOLD1 = canny_threshold1self.CANNY_THRESHOLD2 = canny_threshold2self.save_images = save_imagesself.output_path = output_pathdef _remove_alpha_channel(self, image):"""移除圖像的alpha通道:param image: 輸入圖像:return: 移除alpha通道后的圖像"""if image.shape[2] == 4: # 如果圖像有alpha通道alpha_channel = image[:, :, 3]rgb_channels = image[:, :, :3]# 創建一個白色背景white_background = np.ones_like(rgb_channels, dtype=np.uint8) * 255# 使用alpha混合圖像與白色背景alpha_factor = alpha_channel[:, :, np.newaxis] / 255.0image_no_alpha = rgb_channels * alpha_factor + white_background * (1 - alpha_factor)return image_no_alpha.astype(np.uint8)else:return imagedef _get_gaussian_blur_image(self, image):"""對圖像進行高斯模糊處理:param image: 輸入圖像:return: 高斯模糊處理后的圖像"""return cv2.GaussianBlur(image, self.GAUSSIAN_BLUR_KERNEL_SIZE, self.GAUSSIAN_BLUR_SIGMA_X)def _get_canny_image(self, image):"""對圖像進行Canny邊緣檢測:param image: 輸入圖像:return: Canny邊緣檢測后的圖像"""return cv2.Canny(image, self.CANNY_THRESHOLD1, self.CANNY_THRESHOLD2)def _get_contours(self, image):"""獲取圖像的輪廓:param image: 輸入圖像:return: 輪廓列表"""contours, _ = cv2.findContours(image, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)return contoursdef _get_contour_area_threshold(self, image_width, image_height):"""計算輪廓面積閾值:param image_width: 圖像寬度:param image_height: 圖像高度:return: 最小和最大輪廓面積閾值"""contour_area_min = (image_width * 0.15) * (image_height * 0.25) * 0.8contour_area_max = (image_width * 0.15) * (image_height * 0.25) * 1.2return contour_area_min, contour_area_maxdef _get_arc_length_threshold(self, image_width, image_height):"""計算輪廓弧長閾值:param image_width: 圖像寬度:param image_height: 圖像高度:return: 最小和最大弧長閾值"""arc_length_min = ((image_width * 0.15) + (image_height * 0.25)) * 2 * 0.8arc_length_max = ((image_width * 0.15) + (image_height * 0.25)) * 2 * 1.2return arc_length_min, arc_length_maxdef _get_offset_threshold(self, image_width):"""計算偏移量閾值:param image_width: 圖像寬度:return: 最小和最大偏移量閾值"""offset_min = 0.2 * image_widthoffset_max = 0.85 * image_widthreturn offset_min, offset_maxdef _is_image_file(self, file_path: str) -> bool:"""檢查字符串是否是有效的圖像文件路徑"""valid_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff')return os.path.isfile(file_path) and file_path.lower().endswith(valid_extensions)def _is_base64(self, s: str) -> bool:"""檢查字符串是否是有效的 base64 編碼"""try:if isinstance(s, str):# Strip out data URI scheme if presentif "data:" in s and ";" in s:s = s.split(",")[1]base64.b64decode(s)return Truereturn Falseexcept Exception:return Falsedef _read_image(self, image_source: Union[str, bytes], imread_flag: Optional[int] = None) -> np.ndarray:"""讀取圖像:param image_source: 圖像路徑或base64編碼:param imread_flag: cv2.imread 和 cv2.imdecode 的標志參數 (默認: None):return: 讀取的圖像"""if isinstance(image_source, str):if self._is_image_file(image_source): # 如果是文件路徑if imread_flag is not None:return cv2.imread(image_source, imread_flag)else:return cv2.imread(image_source)elif self._is_base64(image_source): # 如果是 base64 編碼# 剝離數據URI方案(如果存在)if "data:" in image_source and ";" in image_source:image_source = image_source.split(",")[1]img_data = base64.b64decode(image_source)img_array = np.frombuffer(img_data, np.uint8)if imread_flag is not None:image = cv2.imdecode(img_array, imread_flag)else:image = cv2.imdecode(img_array, cv2.IMREAD_UNCHANGED)if image is None:raise ValueError("Failed to decode base64 image")return imageelse:raise ValueError("The provided string is neither a valid file path nor a valid base64 string")else:raise ValueError("image_source must be a file path or base64 encoded string")def get_slider_offset(self, background_source: Union[str, bytes], slider_source: Union[str, bytes],out_file_name: str = None) -> int:"""獲取滑塊的偏移量:param background_source: 背景圖像路徑或base64編碼:param slider_source: 滑塊圖像路徑或base64編碼:param out_file_name: 輸出圖片的文件名: 默認為當前時間戳:return: 滑塊的偏移量"""background_image = self._read_image(background_source)slider_image = self._read_image(slider_source, cv2.IMREAD_UNCHANGED)out_file_name = out_file_name if out_file_name else datetime.now().strftime('%Y%m%d%H%M%S.%f')[:-3]if background_image is None:raise ValueError("Failed to read background image")if slider_image is None:raise ValueError("Failed to read slider image")slider_image_no_alpha = self._remove_alpha_channel(slider_image)image_height, image_width, _ = background_image.shapeimage_gaussian_blur = self._get_gaussian_blur_image(background_image)image_canny = self._get_canny_image(image_gaussian_blur)contours = self._get_contours(image_canny)if self.save_images:# 創建輸出目錄if not os.path.exists(self.output_path):os.makedirs(self.output_path)cv2.imwrite(os.path.join(self.output_path, f'{out_file_name}_image_canny.png'), image_canny)cv2.imwrite(os.path.join(self.output_path, f'{out_file_name}_image_gaussian_blur.png'), image_gaussian_blur)contour_area_min, contour_area_max = self._get_contour_area_threshold(image_width, image_height)arc_length_min, arc_length_max = self._get_arc_length_threshold(image_width, image_height)offset_min, offset_max = self._get_offset_threshold(image_width)offset = Nonefor contour in contours:x, y, w, h = cv2.boundingRect(contour)if contour_area_min < cv2.contourArea(contour) < contour_area_max and \arc_length_min < cv2.arcLength(contour, True) < arc_length_max and \offset_min < x < offset_max:cv2.rectangle(background_image, (x, y), (x + w, y + h), (0, 0, 255), 2)offset = x# 匹配滑塊模板在背景中的位置result = cv2.matchTemplate(background_image, slider_image_no_alpha, cv2.TM_CCOEFF_NORMED)_, _, _, max_loc = cv2.minMaxLoc(result)slider_x, slider_y = max_locoffset = slider_xcv2.rectangle(background_image, (slider_x, slider_y),(slider_x + slider_image_no_alpha.shape[1], slider_y + slider_image_no_alpha.shape[0]),(255, 0, 0), 2)if self.save_images:cv2.imwrite(os.path.join(self.output_path, f'{out_file_name}_image_label.png'), background_image)return offset
2.2 代碼說明
- 圖像預處理:通過高斯模糊和Canny邊緣檢測增強圖像的對比度和亮度,提高滑塊識別率。
- 多圖像融合:通過多次處理圖像并融合結果,以減小噪聲對檢測結果的影響。
- 動態調整閾值:根據圖像的直方圖動態調整Canny邊緣檢測的閾值,提高對不同圖像的適應性。
- 輪廓檢測:通過
_get_contours
函數獲取圖像的輪廓,并根據輪廓面積和弧長進行篩選。 - 滑塊匹配:通過模板匹配方法
cv2.matchTemplate
匹配滑塊在背景圖中的位置。
2.3 優化策略
- 對比度和亮度增強:通過提高圖像的對比度和亮度,使得滑塊和背景的區別更加明顯,增強滑塊匹配的準確度。
- 多圖像融合:融合多張處理后的圖像,減小單張圖像中的噪聲對結果的影響。
- 動態調整參數:根據圖像內容動態調整Canny邊緣檢測的閾值,使得算法對不同類型的圖像都有較好的適應性。
2.4 安裝依賴
要運行上述代碼,需要安裝以下 Python 庫:
pip install numpy opencv-python slider_captcha_match
2.5 使用方法
在安裝完所需庫后,您可以按照以下步驟使用滑塊驗證碼匹配功能:
- 初始化SliderCaptchaMatch類:配置高斯模糊、Canny邊緣檢測等參數。
- 讀取背景圖像和滑塊圖像:可以是文件路徑或base64編碼。
- 獲取滑塊偏移量:調用
get_slider_offset
函數,返回滑塊的準確偏移量。
from slider_captcha_match import SliderCaptchaMatchfrom datetime import datetimeimport cv2# 初始化 SliderCaptchaMatch 類slider_captcha_match = SliderCaptchaMatch(save_images=True,output_path="output")# 讀取背景圖像和滑塊圖像background_source = "path_to_background_image.jpg"slider_source = "path_to_slider_image.png"# 獲取滑塊偏移量offset = slider_captcha_match.get_slider_offset(background_source, slider_source)print(f"滑塊偏移量: {offset}")# 輸出結果保存路徑out_file_name = datetime.now().strftime('%Y%m%d%H%M%S.%f')[:-3]print(f"結果圖像保存路徑: output/{out_file_name}_image_label.png")
三、測試與驗證
為了驗證優化后的滑塊驗證碼匹配技術,進行多次測試,比較不同情況下的滑塊偏移量檢測結果,并記錄背景圖、滑塊圖、中間預處理圖和代碼標注的滑塊位置的圖,以及缺口坐標位置偏移量計算。
Response for row 1: offset(手動標注)=155;缺口坐標(代碼計算)=155.0
?
Response for row 2: offset(手動標注)=119;缺口坐標(代碼計算)=118.5
Response for row 2: offset(手動標注)=223;缺口坐標(代碼計算)=224.0
四、總結
本文介紹了基于圖像處理的滑塊驗證碼匹配技術,并通過多種優化策略提高了滑塊位置偏移量的檢測準確度。通過對圖像進行預處理、融合多張圖像、動態調整閾值等方法,可以有效提高滑塊驗證碼在不同背景下的識別率。希望這篇文章能夠對從事圖像處理和驗證碼研究的讀者有所幫助。
參考資料
- OpenCV 官方文檔
- NumPy 官方文檔
- 本Github項目源碼地址