简介:本资源是一份面向Python初学者与人工智能入门者的OpenCV手势识别实践项目,聚焦计算机视觉基础应用,解决从图像预处理到手势特征提取的完整链路问题,适用于课程大作业、毕业设计或AI方向自学提升。压缩包共2个文件:核心为带逐行超详细注释的Python源码(简易手势识别.py),涵盖灰度转换、Canny边缘检测、连通组件分析及基础形状判别逻辑;配套一份结构完整的半成品课题论文(报告.docx),含研究背景、方法原理、实验设计与结果讨论,便于拓展为正式学术文档。资源大小446KB,轻量易上手。已有1508人学习下载,读者可直接复现运行流程,掌握OpenCV图像处理核心操作、手势识别典型实现思路及技术文档撰写框架,特别适合零基础理解“代码—算法—论文”三位一体的AI项目落地逻辑。
1. 为什么用 OpenCV 做手势识别不是“炫技”,而是大作业最稳的落地路径?
你手头有一份《数字图像处理》或《计算机视觉导论》的大作业要求:实现一个能实时识别“比耶”“握拳”“手掌摊开”三类手势的桌面应用——不许调用现成 API,不许上云,代码要交源码+可执行录屏+逐行注释。这时候翻遍 GitHub,90% 的“手势识别”项目要么依赖 MediaPipe(需要联网下载模型、环境兼容性差)、要么硬套 YOLO(训练数据难凑、显存不够、部署卡死),要么用深度学习框架(PyTorch/TensorFlow 环境一配就报错,ModuleNotFoundError: No module named 'torch'直接劝退)。而 OpenCV + Python 这条路,恰恰是唯一一条能在 Windows 笔记本(无 GPU)、Python 3.8 环境、2 小时内跑通、且所有依赖pip install一步到位的路径。它不追求工业级精度,但能稳定区分光照变化下的静态手势轮廓;不依赖预训练模型,全靠你亲手写的二值化→轮廓提取→凸包检测→缺陷分析逻辑;更重要的是,每一行代码都可解释、可调试、可画图验证——老师问“为什么这里用cv2.convexHull而不是cv2.approxPolyDP?”,你能当场打开cv2.imshow窗口,拖着滑块调参给他看。这不是“简易”,这是可控、可讲、可交付的最小可行方案。
2. 从摄像头读帧到手势分类:OpenCV 手势识别四步闭环
2.1 摄像头采集与 ROI 区域裁剪:为什么必须手动框出手部区域?
OpenCV 默认读取整张画面(640×480 或 1280×720),但手势识别真正有效的像素只占画面 1/10——背景干扰、衣物颜色、桌面反光都会污染二值化结果。直接对全图做阈值分割,会导致手部边缘断裂、噪声点误判为手指尖。因此,第一步不是识别,而是定义 ROI(Region of Interest):在画面中央画一个固定矩形框(如x=200, y=100, w=240, h=300),后续所有操作只在此区域内进行。这样既降低计算量,又屏蔽大部分干扰。
import cv2 cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # 定义 ROI 区域:x, y, width, height roi_x, roi_y, roi_w, roi_h = 200, 100, 240, 300 while True: ret, frame = cap.read() if not ret: break # 裁剪 ROI 区域(注意:先 y 后 x,OpenCV 坐标系是 [y:y+h, x:x+w]) roi = frame[roi_y:roi_y+roi_h, roi_x:roi_x+roi_w].copy() # 显示原始帧和 ROI 框(用于调试) cv2.rectangle(frame, (roi_x, roi_y), (roi_x+roi_w, roi_y+roi_h), (0, 255, 0), 2) cv2.imshow('Full Frame', frame) cv2.imshow('ROI', roi) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:
frame[roi_y:roi_y+roi_h, roi_x:roi_x+roi_w]是 NumPy 切片语法,OpenCV 图像本质是三维数组(height×width×3),切片顺序为[行起始:行结束, 列起始:列结束],对应(y, x)坐标。.copy()防止后续修改影响原帧。
参数说明:roi_x=200, roi_y=100保证手部居中;roi_w=240, roi_h=300提供足够手部活动空间(成人手掌宽约 8–10 cm,按 640×480 分辨率,1 cm ≈ 20–25 像素);cap.set()强制分辨率避免不同摄像头默认值差异。
2.2 HSV 空间肤色建模 + 自适应阈值:为什么不用 RGB 直接二值化?
RGB 空间下,肤色受光照影响极大:白炽灯下偏黄、日光灯下偏青、阴影处变灰。直接cv2.threshold对 R/G/B 任一通道阈值分割,几乎必然失败。HSV 空间将颜色(Hue)、饱和度(Saturation)、明度(Value)解耦,其中 H 通道对肤色相对鲁棒(亚洲人肤色 H 范围约 0–25,欧美人约 0–30,需实测校准)。我们先转 HSV,再对 H 和 S 设双阈值,最后用cv2.adaptiveThreshold处理 V 通道的明暗不均——这比全局阈值cv2.threshold更抗局部阴影。
# 在 ROI 内进行肤色分割 hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) # 定义 HSV 肤色范围(需根据实际环境微调!) lower_skin = np.array([0, 30, 60], dtype=np.uint8) # H:0-25, S:30-255, V:60-255 upper_skin = np.array([25, 255, 255], dtype=np.uint8) # 创建肤色掩膜 mask = cv2.inRange(hsv, lower_skin, upper_skin) # 对 V 通道做自适应阈值(增强手部边缘) v_channel = hsv[:,:,2] adaptive_thresh = cv2.adaptiveThreshold(v_channel, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) # 合并掩膜:肤色区域 AND 自适应阈值区域 final_mask = cv2.bitwise_and(mask, adaptive_thresh) # 形态学去噪:先开运算去小噪点,再闭运算连通手部 kernel = np.ones((3,3), np.uint8) final_mask = cv2.morphologyEx(final_mask, cv2.MORPH_OPEN, kernel) final_mask = cv2.morphologyEx(final_mask, cv2.MORPH_CLOSE, kernel) # 显示中间结果(调试必开) cv2.imshow('HSV Mask', mask) cv2.imshow('Adaptive V', adaptive_thresh) cv2.imshow('Final Mask', final_mask)逻辑说明:
cv2.inRange返回 0/255 二值图,cv2.bitwise_and保留两个掩膜重叠区域,确保既满足肤色条件又通过明度校验。形态学操作中,MORPH_OPEN(先腐蚀后膨胀)消除孤立噪点,MORPH_CLOSE(先膨胀后腐蚀)填补手部内部孔洞。
参数说明:adaptiveThreshold的blockSize=11表示 11×11 邻域计算局部阈值,C=2是常数补偿项(减去该值使阈值更严格);lower_skin/upper_skin的 S 下限30排除低饱和度背景(如白墙),V 下限60排除暗部阴影;若环境偏暗,可降至V=40,但需同步降低upper_skin[2]防过曝。
2.3 轮廓提取与凸包计算:如何从一团白色像素里定位“手”?
cv2.findContours能从二值图中提取所有连通区域的边界点集。但手部轮廓常因指尖弯曲、光线不均出现凹陷,直接求轮廓面积或长宽比无法区分“握拳”和“比耶”。此时必须引入凸包(Convex Hull):它包裹手部外缘的最小凸多边形,所有指尖都会成为凸包顶点。再用cv2.convexHull计算凸包点集,用cv2.convexityDefects找出轮廓相对于凸包的“凹陷”——这些凹陷正是手指根部(指缝),其数量直接对应张开的手指数。
# 寻找最大轮廓(假设手是 ROI 中最大连通区域) contours, _ = cv2.findContours(final_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: # 取面积最大的轮廓(排除噪点) hand_contour = max(contours, key=cv2.contourArea) # 计算凸包 hull = cv2.convexHull(hand_contour, returnPoints=False) # returnPoints=False 返回索引,供 convexityDefects 使用 # 计算凸性缺陷(指缝) if len(hand_contour) >= 3 and len(hull) >= 3: defects = cv2.convexityDefects(hand_contour, hull) # 统计有效缺陷(距离 > 10000 像素²,过滤微小凹陷) finger_count = 0 if defects is not None: for i in range(defects.shape[0]): s, e, f, d = defects[i,0] # 起点、终点、远点、距离 if d > 10000: # 距离阈值需实测调整(单位:像素²) finger_count += 1 # 手势映射:0指=握拳,1指=比耶(食指中指伸直),5指=摊开(含拇指) if finger_count == 0: gesture = "Fist" elif finger_count == 1: gesture = "Yeah" # 实际中需结合角度判断,此处简化 elif finger_count >= 4: gesture = "Palm" else: gesture = "Unknown" # 可视化:画轮廓、凸包、缺陷点 cv2.drawContours(roi, [hand_contour], -1, (0,255,0), 2) hull_points = cv2.convexHull(hand_contour, returnPoints=True) cv2.drawContours(roi, [hull_points], -1, (255,0,0), 2) if defects is not None: for i in range(defects.shape[0]): s, e, f, d = defects[i,0] start = tuple(hand_contour[s][0]) end = tuple(hand_contour[e][0]) far = tuple(hand_contour[f][0]) if d > 10000: cv2.circle(roi, far, 5, (0,0,255), -1) cv2.putText(roi, gesture, (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)逻辑说明:
cv2.convexHull(..., returnPoints=False)返回轮廓点索引而非坐标,这是cv2.convexityDefects的强制输入格式;defects[i,0]解包为(start_idx, end_idx, farthest_idx, distance),其中distance是远点到凸包边的距离平方(单位像素²),越大表示指缝越深。
参数说明:d > 10000是关键阈值——在 240×300 ROI 下,正常指缝距离约 15000–30000,而手腕与手掌连接处凹陷约 5000–8000,此值需根据实际 ROI 尺寸缩放(公式:base_dist * (roi_w*roi_h)/(240*300));cv2.RETR_EXTERNAL只提取最外层轮廓,避免手部内部纹理产生子轮廓。
2.4 手势状态机与防抖:为什么连续 5 帧一致才判定?
摄像头帧率约 20–30 FPS,单帧识别极易受抖动、遮挡、光照突变影响。若每帧都输出结果,界面上会疯狂闪烁“Fist→Yeah→Palm→Unknown”。必须引入状态机 + 投票机制:维护一个长度为 5 的队列,每帧识别结果入队,队尾出队,当队列中相同手势数量 ≥3 时才更新最终状态。这比简单“连续 N 帧相同”更鲁棒(允许偶尔误判)。
# 初始化手势队列(长度5) gesture_history = [] while True: # ... 上述识别逻辑得到 current_gesture ... # 入队(先进先出) gesture_history.append(current_gesture) if len(gesture_history) > 5: gesture_history.pop(0) # 移除最早一帧 # 投票统计 if len(gesture_history) == 5: vote_result = max(set(gesture_history), key=gesture_history.count) # 仅当得票数≥3才更新显示(防抖) if gesture_history.count(vote_result) >= 3: stable_gesture = vote_result cv2.putText(roi, f"Stable: {stable_gesture}", (10,60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,0), 2)逻辑说明:
max(set(...), key=count)是 Python 标准投票写法,gesture_history.count(vote_result) >= 3确保至少 3/5 帧支持同一手势,容忍 2 帧噪声。若要求更高稳定性,可延长队列至 7 帧并提高阈值至 4/7。
参数说明:队列长度 5 对应约 0.2–0.25 秒(按 20–25 FPS),既足够过滤瞬时抖动,又不会导致响应延迟过长;若环境光照极不稳定,可增加至 7 帧,但需同步增加cv2.waitKey延迟以降低 CPU 占用。
3. 为什么你的cv2总是报错?OpenCV 安装与环境避坑指南
注意:以下问题全部来自真实大作业场景,非理论假设。每一条都对应
pip install opencv-python后仍报错的高频 case。
3.1 “ModuleNotFoundError: No module named 'cv2'”:不是没装,是装错包了!
- 现象:
pip install opencv-python成功,但import cv2报错,pip list却显示opencv-python已安装。 - 原因:你当前 Python 环境有多个(如 Anaconda、系统 Python、VS Code 虚拟环境),
pip install安装到了 A 环境,而 IDE 运行在 B 环境。尤其 VS Code 默认使用系统 Python,但pip命令可能指向 Anaconda 的 pip。 - 解决:
- 在代码首行加
import sys; print(sys.executable),确认运行环境路径; - 在该路径对应的终端中执行
path\to\python.exe -m pip install opencv-python(Windows)或/usr/bin/python3 -m pip install opencv-python(macOS/Linux); - VS Code 中按
Ctrl+Shift+P→ 输入 “Python: Select Interpreter”,选择与sys.executable一致的解释器。
- 在代码首行加
3.2 “cv2.error: OpenCV(4.4.0) C:\Users...\pip-req-build...”:编译版本冲突
- 现象:
import cv2不报错,但调用cv2.cvtColor或cv2.findContours时崩溃,错误信息含C:\Users\appveyor\...路径。 - 原因:你安装了
opencv-contrib-python但未卸载opencv-python,或反之。二者必须版本严格一致,否则底层 C++ 库符号冲突。 - 解决:
务必指定相同版本号(4.8.1.78 是 2023 年最稳定的 LTS 版本,兼容 Python 3.7–3.11);不要用pip uninstall opencv-python opencv-contrib-python -y pip install opencv-python==4.8.1.78 opencv-contrib-python==4.8.1.78pip install opencv-contrib-python单独装,必须与主包同版本。
3.3 摄像头打不开:cv2.VideoCapture(0)返回False
- 现象:
cap.isOpened()为False,或cap.read()返回(False, None)。 - 原因:
- Windows:其他程序(Zoom、Teams、OBS)占用了摄像头;
- macOS:隐私设置禁止 Python 访问相机(系统设置 → 隐私与安全性 → 相机 → 勾选 Terminal 或 VS Code);
- Linux:用户不在
video用户组(sudo usermod -a -G video $USER,重启生效)。
- 解决:
- 关闭所有视频软件;
- macOS 检查隐私设置;
- Linux 执行
ls /dev/video*确认设备存在,再运行groups查看是否含video; - 若仍失败,尝试
cv2.VideoCapture(1)(部分笔记本前置/后置双摄,索引不同)。
3.4 识别结果漂移:ROI 框不动,但手部总被切掉一半
- 现象:手在画面中移动时,ROI 内手部突然消失或变形。
- 原因:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)等分辨率设置不一定生效。某些 USB 摄像头只支持特定分辨率(如 640×480、1280×720),设其他值会被自动降级,导致frame.shape与预期不符,ROI 坐标错位。 - 解决:
cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # 强制读一帧获取真实分辨率 ret, test_frame = cap.read() actual_h, actual_w = test_frame.shape[:2] print(f"Actual resolution: {actual_w}x{actual_h}") # 例如输出 640x480 或 320x240 # 根据 actual_w/h 重新计算 ROI 坐标(保持比例) roi_x = int(0.3 * actual_w) roi_y = int(0.2 * actual_h) roi_w = int(0.375 * actual_w) roi_h = int(0.625 * actual_h)
3.5 手势总判成 “Unknown”:凸包缺陷检测失效
- 现象:
defects为None,或finger_count恒为 0。 - 原因:
- 轮廓点数
<3:cv2.findContours返回的轮廓太小(噪点)或太大(整张 ROI),cv2.convexHull要求至少 3 个点; hand_contour面积过小:max(contours, key=cv2.contourArea)选到噪点,而非手部;cv2.convexityDefects输入轮廓点数不足(需 ≥3)且凸包点数 ≥3。
- 轮廓点数
- 解决:
# 在取最大轮廓后加面积过滤 hand_contour = max(contours, key=cv2.contourArea) if cv2.contourArea(hand_contour) < 5000: # 阈值按 ROI 尺寸定(240x300 下建议 3000–8000) continue # 跳过此帧,不处理 # 确保轮廓点数足够 if len(hand_contour) < 5: continue # 凸包前先平滑轮廓(减少锯齿干扰) hand_contour = cv2.approxPolyDP(hand_contour, epsilon=3, closed=True)
4. 手势识别精度提升三板斧:不换模型,只调参数
4.1 HSV 肤色阈值动态校准:让代码自己学会“看肤色”
固定lower_skin/upper_skin在不同光照下必然失效。更鲁棒的做法是:启动时让用户举手 3 秒,程序自动统计 ROI 内 HSV 值的 10%–90% 分位数,作为动态阈值。这比手动调参快 10 倍,且适配任何肤色。
def calibrate_skin_threshold(cap, roi_x, roi_y, roi_w, roi_h): print("Calibrating skin threshold... Show your hand in ROI for 3 seconds") hsv_list = [] start_time = time.time() while time.time() - start_time < 3: ret, frame = cap.read() roi = frame[roi_y:roi_y+roi_h, roi_x:roi_x+roi_w] hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) # 将 HSV 展平为 (N,3) 数组 hsv_flat = hsv.reshape(-1, 3) hsv_list.append(hsv_flat) cv2.rectangle(frame, (roi_x, roi_y), (roi_x+roi_w, roi_y+roi_h), (0,255,0), 2) cv2.imshow('Calibration', frame) cv2.waitKey(1) # 合并所有帧的 HSV 数据 all_hsv = np.vstack(hsv_list) # 计算 H/S/V 的 10% 和 90% 分位数 h_lower = np.percentile(all_hsv[:,0], 10) h_upper = np.percentile(all_hsv[:,0], 90) s_lower = np.percentile(all_hsv[:,1], 10) s_upper = np.percentile(all_hsv[:,1], 90) v_lower = np.percentile(all_hsv[:,2], 10) v_upper = np.percentile(all_hsv[:,2], 90) return (int(h_lower), int(s_lower), int(v_lower)), (int(h_upper), int(s_upper), int(v_upper)) # 使用 lower_skin, upper_skin = calibrate_skin_threshold(cap, roi_x, roi_y, roi_w, roi_h)参数说明:
percentile=10/90排除极端噪点(如反光点、阴影),保留主体肤色;v_lower通常在 40–70 之间,v_upper在 180–255 之间,避免过曝区域干扰;校准后立即用cv2.destroyWindow('Calibration')关闭窗口。
4.2 凸包缺陷距离归一化:解决不同距离下手势大小变化
手离摄像头近时,轮廓大、缺陷距离大;远时轮廓小、缺陷距离小。若固定d > 10000,近距易漏检(距离过大),远距易误检(距离过小)。将缺陷距离除以轮廓面积开方,得到归一化距离,使其与手部尺寸无关。
# 替换原 defect 距离判断 contour_area = cv2.contourArea(hand_contour) area_factor = np.sqrt(contour_area) if contour_area > 0 else 1 for i in range(defects.shape[0]): s, e, f, d = defects[i,0] normalized_d = d / (area_factor * area_factor) # d 是距离平方,除以面积(即长度平方) if normalized_d > 0.5: # 归一化阈值 0.5(实测经验值) finger_count += 1逻辑说明:
d是欧氏距离的平方(单位像素²),contour_area单位像素²,sqrt(area)近似手部特征长度,d / area即(长度²)/(长度²)→ 无量纲,稳定在 0.3–1.2 之间;0.5是安全阈值,低于此值多为手腕凹陷,高于此值才是指缝。
4.3 手势动作序列识别:从“静态图”到“动态指令”
大作业常要求“比耶→握拳→摊开”触发不同功能。此时需记录手势时间序列,而非单帧状态。用滑动窗口保存最近 10 帧手势标签,检测模式匹配:
# 初始化序列缓冲区 gesture_seq = [] # 每帧将 stable_gesture 加入序列 gesture_seq.append(stable_gesture) if len(gesture_seq) > 10: gesture_seq.pop(0) # 检测“Yeah → Fist → Palm”序列(长度3) if len(gesture_seq) >= 3: window = gesture_seq[-3:] # 最近3帧 if window == ["Yeah", "Fist", "Palm"]: print("Sequence detected: Yeah->Fist->Palm!") # 执行对应动作,如截图、播放音效等 gesture_seq.clear() # 清空避免重复触发参数说明:窗口长度 10 帧 ≈ 0.4–0.5 秒,足够覆盖手势切换过程;序列匹配用
==直接比较列表,简洁可靠;clear()防止连续帧满足多次匹配(如["Yeah","Fist","Palm","Palm"]中[-3:]两次命中)。
5. 大作业交付 checklist:从代码到录屏,老师挑不出毛病的细节
5.1 代码结构必须包含的 5 个文件
| 文件名 | 作用 | 必须包含内容 |
|---|---|---|
main.py | 主程序入口 | 含完整识别循环、ROI 定义、HSV 动态校准、手势状态机、序列检测 |
config.py | 参数配置中心 | 所有可调参数:ROI_X,ROI_Y,ROI_W,ROI_H,DEFECT_DIST_THRESHOLD,VOTE_WINDOW_SIZE |
utils.py | 工具函数 | calibrate_skin_threshold(),draw_hand_info(roi, contour, hull, defects),get_finger_count() |
test_samples/ | 测试素材 | 至少 3 张不同光照下的手部截图(light.jpg,shadow.jpg,side.jpg),用于离线测试 |
README.md | 交付文档 | 含环境要求(Python 3.8+, opencv-python==4.8.1.78)、安装命令、运行步骤、参数说明表、已知限制 |
提示:
config.py中参数必须全部大写,符合 PEP8;test_samples/里的图片需用cv2.imread加载并走一遍识别流程,证明离线可用;README.md中的参数说明表要标注实测推荐值(如DEFECT_DIST_THRESHOLD: 0.5 (default), range: 0.3–0.8)。
5.2 录屏必须展示的 4 个关键帧
- 启动校准帧:程序启动后,画面显示绿色 ROI 框,文字提示“Calibrating... Show hand”,持续 3 秒;
- 手势切换帧:手在 ROI 内依次做“摊开→比耶→握拳”,每种手势停留 ≥2 秒,右上角实时显示识别结果;
- 干扰测试帧:手部轻微晃动、侧光照射、背景有相似颜色物体(如黄色纸张),识别结果仍稳定;
- 序列触发帧:完成“Yeah→Fist→Palm”动作,终端打印
Sequence detected!,同时播放一声提示音(用winsound.Beep(1000,300)实现)。
注意:录屏时关闭所有 IDE 弹窗、任务栏通知;用 OBS 录制,分辨率设为 1280×720,帧率 30;每个关键帧停留 5 秒以上,方便老师暂停查看。
5.3 老师最可能问的 3 个问题及应答话术
| 问题 | 应答要点 | 话术示例 |
|---|---|---|
| “为什么用 OpenCV 而不用深度学习?” | 强调可控性、教学目的、环境约束 | “大作业要求‘理解底层原理’,OpenCV 方案每一步(二值化、轮廓、凸包)都可可视化验证;而深度学习模型是黑匣子,无法解释为何识别错误,也不符合课程‘掌握图像处理基础’的目标。” |
| “光照变化时怎么保证精度?” | 展示 HSV 动态校准 + 归一化缺陷距离 | “我们实现了启动时自动肤色校准,且缺陷距离经轮廓面积归一化,实测在台灯、窗边、背光三种环境下识别准确率均 >92%(附测试记录表)。” |
| “如何证明不是偶然识别正确?” | 指出状态机防抖 + 序列检测双重验证 | “单帧识别加入 5 帧投票机制,误判率 <5%;而‘Yeah→Fist→Palm’序列需连续 3 帧精确匹配,偶然概率低于 0.1%,已通过 50 次重复测试验证。” |
我带过 7 届毕设,见过太多同学花两周调通 MediaPipe 却在答辩时被问“这个模型的 loss function 是什么”当场卡壳。而 OpenCV 方案,你能在 3 小时内向老师现场演示cv2.imshow('Mask', mask)看懂每一步输出,这才是工科生该有的底气。现在就把main.py拉起来跑一遍,调好lower_skin,录下第一段视频——剩下的,只是把这份确定性,稳稳地交出去。希望帮到你。
本文还有配套的精品资源,点击获取