【python OpenCV3.3 图像处理教程:直线检测、圆检测、对象测量、腐蚀、膨胀等形态学操作、数字验证码识别、人脸检测
1. 直线检测
Hough Line Transform:前提:边缘检测已经完成,基于霍夫变换
1.1 原理
可以通过(theta,r)唯一表示一个点。
把过三个点的全部直线以某一角度全部计算出来,如果三个点的直线有相同的,则说明有一条直线过了这三个点。
至于为啥用(theta, r)而不是斜率k和截距b来表示一条直线,是因为利用y = kx + b来表示直线时,存在斜率k无穷大的情况,无法计算。并且theta为0到2*pi, 且对于直线来说r一定小于等于b,所以计算的数值也相对较小。
参考博客
def line_detection(image):blurred = cv.GaussianBlur(image, (3, 3), 0)gray =cv.cvtColor(image,cv.COLOR_BGR2GRAY)# edges=cv.Canny(gray,50,150,apertureSize=3) #apertureSize类似于步长# edges=cv.Canny(blurred,50,150,apertureSize=3) #apertureSize类似于步长edges=cv.Canny(image,50,150,apertureSize=3) #apertureSize类似于步长lines =cv.HoughLines(edges,1,np.pi/180,200)#200:threshold,累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。# 大于阈值threshold的线段才可以被检测通过并返回到结果中。# ---------------------------# 自己计算过程如下:for line in lines:rho,theta=line[0] #r,角度 霍夫空间a=np.cos(theta) #余弦值b=np.sin(theta)x0=a*rho #这个点的x坐标y0=b*rho #这个点的y坐标# x1 stores the rounded off value of (rcos(theta)-1000sin(theta))x1 = int(x0 + 1000 * (-b)) #其实就是x0,y0这个点按比例增加或者减少一段距离,还在这条直线上的点x1,y1# y1 stores the rounded off value of (rsin(theta)+1000cos(theta))y1 = int(y0 + 1000 * (a))# x2 stores the rounded off value of (rcos(theta)+1000sin(theta))x2 = int(x0 - 1000 * (-b))# y2 stores the rounded off value of (rsin(theta)-1000cos(theta))y2 = int(y0 - 1000 * (a))cv.line(image,(x1,y1),(x2,y2),(0,0,255),2)cv.imshow("line detect image",image)
def line_detection_possible(image):gray =cv.cvtColor(image,cv.COLOR_BGR2GRAY)edges=cv.Canny(gray,50,150,apertureSize=3) #apertureSize类似于步长lines =cv.HoughLinesP(edges,1,np.pi/180,200,minLineLength=20,maxLineGap=5) #允许断点,可以定义最小长度for line in lines:x1,y1,x2,y2=line[0] #r,角度 霍夫空间cv.line(image,(x1,y1),(x2,y2),(0,0,255),2)cv.imshow("line detect possible image",image)
2. 圆检测
直线检测、圆检测:基于霍夫变换
霍夫圆检测对噪声比较敏感,所以要先进行滤波(去噪)
霍夫圆检测基于梯度变换:
- 检测边缘,发现可能的圆心
- 从候选圆心开始计算最佳半径大小
def detect_CIRCLES(image):dst=cv.pyrMeanShiftFiltering(image,10,100)#均值滤波# dst=cv.GaussianBlur(image,(3,3),0)#高斯滤波img=cv.cvtColor(dst,cv.COLOR_BGR2GRAY)circles=cv.HoughCircles(img,cv.HOUGH_GRADIENT,1,20,param1=50,param2=55,minRadius=0,maxRadius=0)#距圆心半径为20以内归为1个圆,param1较高的阈值传递到Canny边缘检测器,# param2它是检测阶段圆中心的累加器阈值。它越小,越多。 可能会检测到假圆圈。circles=np.uint16(np.around(circles))for i in circles[0,:]:cv.circle(image,(i[0],i[1]),i[2],[0,0,255],2)#在原图上画圆cv.circle(image,(i[0],i[1]),2,[255,0,0],2)#画圆心cv.imshow("circles:",image)cv.imshow("circles1:",dst)
3. 对象测量
- cv.findContours():获取轮廓
- cv.contourArea() :#计算轮廓面积
- cv.boundingRect() :计算轮廓外接矩形
- cv.moments() : 计算轮廓的几何矩
- approxPolyDP(curve, epsilon, closed, approxCurve=None):多边形逼近函数
参数curve表示输入的点集,直接使用轮廓点集contour
参数epsilon表示指定的精度,即原始曲线与近似曲线之间的最大距离
参数closed表示若为true,则说明近似曲线是闭合的,反之,若为false,则断开
参数approxCurve表示输出的点集,当前点集是能最小包容指定点集的,画出来即是一个多边形
def measure_object(image):gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) # 图像转灰度图# 将灰度图转为二值图 ret是阈值 binary是二值图ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)dst = cv.cvtColor(binary, cv.COLOR_GRAY2BGR) # 边缘检测后再次灰度处理print('threshold value: %s' % ret) # 把阈值打印出来cv.imshow('binary image', binary) # 把二值图显示出来# 找目标的轮廓image, contours, hierarchy = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)for i, contour in enumerate(contours):area = cv.contourArea(contour) # 计算轮廓的面积print('area', area)x, y, w, h = cv.boundingRect(contour) # 计算轮廓外接矩形rate = min(w, h) / max(w, h) # 计算长宽比print('rate', rate)mm = cv.moments(contour) # 计算轮廓的几何矩print(type(mm))# 找轮廓的中心位置# cx = mm['m10'] / mm['m00'] #就是不对# cy = mm['m01'] / mm['m00']# 用个黄色小圆圈把几何图形的中心位置绘制出来# cv.circle(image, (np.int(cx), np.int(cy)), 3, (0, 255, 255), -1)# 用红色框框把外接矩形绘制出来cv.rectangle(dst, (x, y), (x + w, y + h), (0, 0, 255), 2)approxCurve = cv.approxPolyDP(contour, 4, True)"""approxPolyDP(curve, epsilon, closed, approxCurve=None)多边形逼近函数参数curve表示输入的点集,直接使用轮廓点集contour参数epsilon表示指定的精度,即原始曲线与近似曲线之间的最大距离参数closed表示若为true,则说明近似曲线是闭合的,反之,若为false,则断开参数approxCurve表示输出的点集,当前点集是能最小包容指定点集的,画出来即是一个多边形"""# print(approxCurve) # 打印每个轮廓的特征点print(approxCurve.shape) # 打印该点集的shape,第一个数是代表了点的个数,也就是边长连接逼近数if approxCurve.shape[0] >6: # 显示对圆形的多边形逼近cv.drawContours(dst, contours, i, (0, 255, 0), 2)if approxCurve.shape[0] == 4: # 显示对四边形的多边形逼近cv.drawContours(dst, contours, i, (0, 0, 255), 2)if approxCurve.shape[0] == 3: # 显示对三角形的多边形逼近cv.drawContours(dst, contours, i, (255, 0, 0), 2)if approxCurve.shape[0] == 5: # 显示对五边形的多边形逼近cv.drawContours(dst, contours, i, (255, 255, 0), 2)cv.imshow("measure-contours", dst)
4.膨胀和腐蚀
- 膨胀:最大值替换中心像素;cv.dilate
- 腐蚀:最小值替换中心像素 cv.erode
- 膨胀和腐蚀支持任意形状的结构元素
- 3x3、5x5的结构元素或者模板
废话不多说,看效果:如下图对二值图像进行腐蚀和膨胀的效果(如字面意思)
对彩色图像的腐蚀和膨胀效果直观来看其实就是变暗和变亮?
完整代码:
import cv2 as cv
import numpy as np
# 膨胀和腐蚀支持任意形状的结构元素 (膨胀:最大值替换中心像素;腐蚀:最小值替换中心像素)
# 3*3、5*5的结构元素或者模板
data_path='C:\\Users\\22852\\Desktop\\opencv\\data\\'
result_path='C:\\Users\\22852\\Desktop\\opencv\\result\\'
def erode_demo(image):#腐蚀gray=cv.cvtColor(image,cv.COLOR_BGR2GRAY)rect,binary=cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU)cv.imshow("binary:", binary)# cv.imwrite("C:\\Users\\22852\\Desktop\\opencv\\result\\binary_handwriting.jpeg",binary)kernel = cv.getStructuringElement(cv.MORPH_RECT,(3,3))dst = cv.erode(binary,kernel)cv.imshow("eroid:",dst)# cv.imwrite("C:\\Users\\22852\\Desktop\\opencv\\result\\erode_handwriting.jpeg:",dst)def dilate_demo(image):#膨胀gray=cv.cvtColor(image,cv.COLOR_BGR2GRAY)rect,binary=cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU)cv.imshow("binary:",binary)kernel = cv.getStructuringElement(cv.MORPH_RECT,(3,3))dst = cv.dilate(binary,kernel)cv.imshow("dilate:",dst)# cv.imwrite("C:\\Users\\22852\\Desktop\\opencv\\result\\dilate_handwriting.jpeg:", dst)src = cv.imread('C:\\Users\\22852\\Desktop\\opencv\\data\\handwriting.jpeg')
src1 = cv.imread('C:\\Users\\22852\\Desktop\\opencv\\data\\input.jpg')
cv.imshow("input image:", src)
cv.imshow("input image1:", src1)
kernel = cv.getStructuringElement(cv.MORPH_RECT,(5,5))
dst1 =cv.erode(src1,kernel)#对彩色图像进行腐蚀,更暗
dst2 =cv.dilate(src1,kernel)#对彩色图像进行膨胀,更亮
cv.imshow("erode image:", dst1)
cv.imshow("dilate image:", dst2)
# cv.imwrite(result_path+'erode_girl.jpg',dst1)
# cv.imwrite(result_path+'dilate_girl.jpg',dst2)
erode_demo(src)
dilate_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
5.开闭操作
cv.morphologyEx(binary,cv.MORPH_OPEN,kernel)
cv.morphologyEx(binary,cv.MORPH_CLOSE,kernel)
- 开操作:腐蚀+膨胀,运用在灰度图、二值图像分选中,去除小的干扰块,尽量保证其他地方无变化
- 闭操作:膨胀+腐蚀,运用在灰度图、二值图像分选中,填充封闭区域,尽量保证其他地方无变化
- 还可以进行 水平、垂直线提取
开操作效果:
闭操作效果(没有很好的素材)
def open_demo(image):#开操作dst = cv.GaussianBlur(image, (5,5), 0) # 高斯滤波gray=cv.cvtColor(dst,cv.COLOR_BGR2GRAY)rect,binary=cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU)# rect,binary=cv.threshold(gray,0,255,cv.THRESH_BINARY_INV|cv.THRESH_OTSU)cv.imshow("binary:", binary)# cv.imwrite(result_path+"y_binary.jpg:",binary)kernel = cv.getStructuringElement(cv.MORPH_RECT,(3,3))# kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE,(3,3))#圆型# kernel = cv.getStructuringElement(cv.MORPH_RECT,(1,30))#提取垂直线# kernel = cv.getStructuringElement(cv.MORPH_RECT,(45,1))#提取水平线dst = cv.morphologyEx(binary,cv.MORPH_OPEN,kernel)#开操作cv.imshow("open:",dst)cv.imwrite(result_path+"y_open.jpg:",dst)def close_demo(image):#闭合操作dst = cv.GaussianBlur(image, (5, 5), 0) # 高斯滤波gray=cv.cvtColor(dst,cv.COLOR_BGR2GRAY)rect,binary=cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU)cv.imshow("binary:",binary)kernel = cv.getStructuringElement(cv.MORPH_RECT,(15,15))dst = cv.morphologyEx(binary,cv.MORPH_CLOSE,kernel)cv.imshow("close:",dst)# cv.imwrite("C:\\Users\\22852\\Desktop\\opencv\\result\\dilate_handwriting.jpeg:", dst)src = cv.imread('C:\\Users\\22852\\Desktop\\opencv\\data\\y.jpg')
src1 = cv.imread('C:\\Users\\22852\\Desktop\\opencv\\data\\lines.jpg')
# src1 = cv.resize(src, [600, 600])
# cv.namedWindow("input image",cv.WINDOW_AUTOSIZE)
cv.imshow("input image:", src)
# cv.imshow("input image:", src1)open_demo(src)
# close_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
6.其他形态学操作
- 顶帽:原图与开操作之间的差值 得到闭合图像外的杂质?
- 黑帽:原图与闭操作之间的差值 得到原本闭合图像里没被填充的部分(杂质)?
- 形态学梯度(基本梯度):膨胀后的图像减去腐蚀后的图像得到的梯度
- 内梯度:原图-腐蚀后
- 外梯度:膨胀后-原图
顶帽:
def top_hat_demo(image):gray=cv.cvtColor(image,cv.COLOR_BGR2GRAY)rect, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)kernel = cv.getStructuringElement(cv.MORPH_RECT,(3,3))dst = cv.morphologyEx(gray,cv.MORPH_TOPHAT,kernel)#对灰度图dst1 = cv.morphologyEx(binary,cv.MORPH_TOPHAT,kernel)#对二值图cimage = np.array(gray.shape, np.uint8)cimage = 75dst = cv.add(dst, cimage)cv.imshow("top_hat:",dst1)
黑帽:
ef Black_hat_demo(image):gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)rect, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)kernel = cv.getStructuringElement(cv.MORPH_RECT,(15,15))dst = cv.morphologyEx(gray,cv.MORPH_BLACKHAT,kernel)#对灰度图dst1 = cv.morphologyEx(binary,cv.MORPH_BLACKHAT,kernel)#对二值图cimage = np.array(gray.shape, np.uint8)cimage = 55dst=cv.add(dst,cimage)cv.imshow("Blackhat_hat:",dst1)
形态学梯度:
def Gradient_demo(image):gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)rect, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)kernel = cv.getStructuringElement(cv.MORPH_RECT,(3,3))dst = cv.morphologyEx(gray,cv.MORPH_GRADIENT,kernel)#对灰度图dst1 = cv.morphologyEx(binary,cv.MORPH_GRADIENT,kernel)#对二值图cimage = np.array(gray.shape, np.uint8)cimage = 55dst=cv.add(dst,cimage)cv.imshow("Gradient:",dst)
7. 分水岭算法
在该算法中,空间上相邻并且灰度值相近的像素被划分为一个区域。
分水岭算法的整个过程:
- 把梯度图像中的所有像素按照灰度值进行分类,并设定一个测地距离阈值。
- 找到灰度值最小的像素点(默认标记为灰度值最低点),让threshold从最小值开始增长,这些点为起始点。
- 水平面在增长的过程中,会碰到周围的邻域像素,测量这些像素到起始点(灰度值最低点)的测地距离,如果小于设定阈值,则将这些像素淹没,否则在这些像素上设置大坝,这样就对这些邻域像素进行了分类。
- 随着水平面越来越高,会设置更多更高的大坝,直到灰度值的最大值,所有区域都在分水岭线上相遇,这些大坝就对整个图像像素的进行了分区。
在OpenCV中,我们需要给不同区域贴上不同的标签。用大于1的整数表示我们确定为前景或对象的区域,用1表示我们确定为背景或非对象的区域,最后用0表示我们无法确定的区域。然后应用分水岭算法,我们的标记图像将被更新,更新后的标记图像的边界像素值为-1。
分水岭算法步骤:
- 模糊去噪
- 灰度、二值化图像
- morphology operation:开闭操作得到确定的背景区域
- distance transform:通过距离转换得到确定的前景区域,剩余部分为不确定区域
- 对确定的前景图像进行连接组件处理,得到标记图像
- watershed transform:cv.watershed()根据标记图像对原图像应用分水岭算法,更新标记图像
def watershed_demo(src):# 模糊操作blur = cv.pyrMeanShiftFiltering(src, 5, 100)#均值滤波# blur=cv.GaussianBlur(src,(3,3),0)#高斯滤波# 灰度、二值化图像gray = cv.cvtColor(blur, cv.COLOR_BGR2GRAY)ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)cv.imshow("binary", binary)# morphology operationkernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))mb = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel, iterations=2) #开操作迭代两次sure_bg = cv.morphologyEx(mb, cv.MORPH_CLOSE, kernel, iterations=3) #闭操作迭代3次cv.imshow("morphology operation", sure_bg)# distance transformdist = cv.distanceTransform(mb, cv.DIST_L2, 3) #基于L2的距离变换dist_output = cv.normalize(dist, 0, 1, cv.NORM_MINMAX) #归一化cv.imshow("distance_t", dist_output*50)ret, surface = cv.threshold(dist, dist.max()*0.6, 255, cv.THRESH_BINARY)cv.imshow("surface", surface)surface_fg = np.uint8(surface) #float转为intunknown = cv.subtract(sure_bg, surface_fg)ret, markers = cv.connectedComponents(surface_fg)#retval, labelsprint("ret =", ret)# watershed transformmarkers = markers +1markers[unknown == 255] = 0markers = cv.watershed(src, markers = markers)src[markers == -1] = [0, 0, 255]cv.imshow("result", src)
8. 案例:数字验证码识别
import cv2 as cv
import numpy as np
from PIL import Image
# import pytesseract as tess
def recognize_txt(image):gray = cv.cvtColor(image,cv.COLOR_BGR2GRAY)rect,binary = cv.threshold(gray,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU)cv.imshow("binary:", binary)kernel = cv.getStructuringElement(cv.MORPH_RECT,(2,1))binary1 = cv.morphologyEx(binary,cv.MORPH_OPEN,kernel)cv.imshow("binary1:",binary1)kernel = cv.getStructuringElement(cv.MORPH_RECT, (1, 2))binary2 = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel)cv.imshow("binary2:", binary2)cv.bitwise_not(binary2,binary2)#要变为白色背景txt_image=Image.fromarray(binary2)text = tess.image_to_string(txt_image)print("识别结果:%s"%text)src = cv.imread("data/yzm.jpg")
cv.imshow("input image:", src)
recognize_txt(src)
cv.waitKey(0)
cv.destroyAllWindows()
9.案例:人脸检测
需要调用opencv官方的库,比如说”haarcascade_frontalface_alt_tree.xml“
把官方库下载到本地用cv.CascadeClassifier加载
import cv2 as cv
import numpy as np
def face_detect(image):gray=cv.cvtColor(image,cv.COLOR_BGR2GRAY)face_detector= cv.CascadeClassifier("C:\\Users\\22852\\Desktop\\opencv\\opencv-3.3.1\\data\\haarcascades\\haarcascade_frontalface_alt_tree.xml")faces=face_detector.detectMultiScale(gray,1.02,5)# 5:minNeighbors,越高可能检测不到人脸#1.02:scaleFactor for x,y,w,h in faces:cv.rectangle(image,(x,y),(x+w,y+h),(0,255,255),2)cv.imshow("result:",image)
src = cv.imread('C:\\Users\\22852\\Desktop\\opencv\\data\\face.jpg')
cv.namedWindow("input image:",cv.WINDOW_AUTOSIZE)
cv.imshow("input image:", src)
face_detect(src)
也可以直接调摄像头进行视频检测~
import cv2 as cv
import numpy as np
def face_detect(image):gray=cv.cvtColor(image,cv.COLOR_BGR2GRAY)face_detector= cv.CascadeClassifier("C:\\Users\\22852\\Desktop\\opencv\\opencv-3.3.1\\data\\haarcascades\\haarcascade_frontalface_alt_tree.xml")faces=face_detector.detectMultiScale(gray,1.02,5)# 5:minNeighbors,越高可能检测不到人脸#1.02:scaleFactorfor x,y,w,h in faces:cv.rectangle(image,(x,y),(x+w,y+h),(0,255,255),2)cv.imshow("result:",image)
capture = cv.VideoCapture(0)
while(True):ret,frame = capture.read()frame=cv.flip(frame,1)face_detect(frame)c=cv.waitKey(10)if c==27: #ESCbreak
【python OpenCV3.3 图像处理教程:直线检测、圆检测、对象测量、腐蚀、膨胀等形态学操作、数字验证码识别、人脸检测相关推荐
- Python+OpenCV3.3图像处理视频教程 贾志刚 代码笔记3
21 直线检测 import cv2 as cv import numpy as npdef line_detection(image):gray = cv.cvtColor(image, cv.CO ...
- 人脸表情识别/人脸检测/ML/DL/图像处理博主
人脸表情识别/人脸检测/ML/DL/图像处理博主 人脸表情识别/人脸检测/ML/DL/图像处理博主:https://blog.csdn.net/app_12062011/article/categor ...
- Python+OpenCV3.3图像处理视频教程 贾志刚 代码笔记1
2 图像加载与保存 import cv2 as cv import numpy as npdef video_demo():capture = cv.VideoCapture(0)while(True ...
- Python+OpenCV3.3图像处理视频教程 贾志刚 代码笔记2
11 边缘保留滤波(EPF) import cv2 as cv import numpy as npdef bi_demo(image):dst = cv.bilateralFilter(image, ...
- Python+OpenCV3.3图像处理视频教程 贾志刚1
1.概述与环境搭建 pip install opencv-pythonpip install opencv-contrib-pythonpip install pytesseract 测试: impo ...
- 基于Python的KNN数字验证码识别
一.主要内容 本项目基于Python爬虫爬取验证码图片,对图片进行去噪.分割,通过KNN算法训练模型,实现验证其准确率. 二.系统流程 首先从指定的网页中爬取验证码图片数据,然后对数据进行一个去噪和分 ...
- 【学习笔记】opencv的python接口 形态学操作 腐蚀 膨胀 通用形态学函数
腐蚀 img=np.zeros((5,5),np.uint8) img[1:4,1:4]=1 kernel=np.ones((3,1),np.uint8) erosion=cv2.erode(img, ...
- Java 图像处理教程(人脸检测,添加水印,图像颜色转换)
Java 图像处理教程(人脸检测,添加水印,图像颜色转换) 文章目录 Java 图像处理教程(人脸检测,添加水印,图像颜色转换) 1:图片的读和写 2:彩色图像转换成灰度图像 3:彩色图像转换成负图像 ...
- Python OpenCV3 计算机视觉秘籍:1~5
原文:OpenCV 3 Computer Vision with Python Cookbook 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自[ApacheCN 计算机视觉 译文集],采 ...
最新文章
- 使用C#创建SQLite控制台应用程序
- C++ STL容器总结之vector(超详细版)
- boost::phoenix::arg_names::arg1用法的测试程序
- Sentry UDFWhiteList bug分析
- 160809230张钊
- 关于.net framework 1.1 与 .net framework 2.0的小问题
- C++primer 第 3 章 字符串、向量和数组 3 . 3 标准库类型vector
- Kubernetes理论02
- Django视图层:视图函数、视图类
- python实验项目_Python3实验 项目结构(文件操作)
- C++语言虚函数表实现多态原理
- JavaScript之语句
- 使用gdb调试出现 No debugging symbols found in a.out
- springboot使用logback
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(53)-工作流设计-我的批阅
- 数独算法-递归与回溯
- 受损固态硬盘(SSD)数据恢复方法(福利:固态硬盘免费恢复数据)
- 大话设计模式(php版)第五章——依赖倒装原则
- 通信原理仿真100例 | 多普勒频移的matlab仿真
- 情人节表白代码:浪漫玫瑰花,俘获少女心