记录

FPGA

时序约束

  1. 如通信实验,时钟频率比较高的实验,需要进行时序约束
  2. 当占用芯片的逻辑资源多的时候,需要使用时序约束:因为FPGA在布线时会优先考虑面积

时钟约束用来描述设计人员对于时序的要求,包括时钟频率和输入输出延时

D触发器

arduino 机械臂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <Servo.h>

Servo myservo_1; // 定义Servo对象来控制
Servo myservo_2; // 定义Servo对象来控制
Servo myservo_3; // 定义Servo对象来控制
int pos_1 = 0; // 角度存储变量
int pos_2 = 0; // 角度存储变量
int pos_3 = 0; // 角度存储变量

void setup() {
myservo_1.attach(12); // 控制线连接数字9
myservo_2.attach(11); // 控制线连接数字9
myservo_3.attach(10); // 控制线连接数字9
}

void loop() {
myservo_1.write(0); // 舵机角度写入
myservo_2.write(0); // 舵机角度写入
myservo_3.write(0); // 舵机角度写入
delay(5000);
for (pos_1 = 0; pos_1 <= 10; pos_1 ++) { // 0°到180°
// in steps of 1 degree
myservo_1.write(pos_1); // 舵机角度写入
delay(5); // 等待转动到指定角度
}
delay(1000);
for (pos_2 = 0; pos_2 <= 60; pos_2 ++) { // 从180°到0°
myservo_2.write(pos_2); // 舵机角度写入
delay(5); // 等待转动到指定角度
}
delay(1000);
for (pos_3 = 0; pos_3 <= 30; pos_3 ++) { // 从180°到0°
myservo_3.write(pos_3); // 舵机角度写入
delay(5); // 等待转动到指定角度
}
delay(5000);

myservo_3.write(0); // 舵机角度写入
delay(1000);
myservo_2.write(0); // 舵机角度写入
delay(1000);
myservo_1.write(-5); // 舵机角度写入


//// for (pos_1 = 0; pos_1 <= 120; pos_1 ++) { // 0°到g180°
//// // in steps of 1 degree
//// myservo_1.write(pos_1); // 舵机角度写入
//// delay(5); // 等待转动到指定角度
//// }
while(1);
}

数字电路中的逻辑值:

  • 逻辑 0:表示低电平,相当于电路 GND。
  • 逻辑 1:表示高电平,相当于电路 VCC。
  • 逻辑 X:表示未知,高或低。
  • 逻辑 Z:表示高阻态,悬空状态

Verilog 中的数字表示:

“(数字的二进制)位宽 + 进制(缩写) + 数值”来表示一个数字。

1
2
3
4
5
//二进制0101的表示
4'b0101

//十进制2的表示,数字2的二进制表示为0010占4位
4'd2

Verilog 的默认二进制位宽为32位,默认的进制为十进制。

当二进制数字位数多的时候可以使用下划线增加可读性,编译时下划线会被去掉。

1
16'b1001_1010_1010_1001

标识符:

Verilog 的标识符可以用于定义模块名、端口名和信号名。

Verilog 的命名规则与 C 语言变量名的命名规则基本相同:只有一点,可以在命名中包含$符号。

标识符规则:

标识符推荐写法:

  • 不建议大小写混合
  • 普通内部信号全部小写
  • 信号命名体现含义
  • 使用下划线区分词
  • 采用前后缀:比如时钟可以采用:clk_50,clk_cpu

数据类型

三种数据类型:

  • 寄存器数据类型:实际电路物理模型

    • 抽象数据存储单元,可以通过赋值语句改变寄存器储存的值

    • 关键字:reg,默认初始值为 X 不确定

    • ```verilog // reg + [位宽:31:0 指32位位宽,高位在前] + 标识符名称(寄存器名称) reg [31:0] delay_cnt; //延时计数使用的寄存器 reg key_reg; //没给位宽时默认位宽为1

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38

      + reg 类型的数据只能在 always 语句和 initial 语句中被赋值

      + 如果 always 中带有时钟信号即过程语句描述的是时序逻辑,则寄存器对应为触发器

      + 如果 always 中不带有时钟信号即过程语句描述的是组合逻辑,则寄存器对应为硬件连线

      +

      + 线网数据类型:实际电路物理模型

      + 参数数据类型:给编译器用的







      ### 图像处理

      OV7725摄像头

      主控器控制OV7725时采用SCCB协议读写其寄存器,而它输出图像时则使用VGA或QVGA时序, 其中VGA在输出图像分辨率为480*640时采用,QVGA是Quarter VGA,其输出分辨率为240*320, 这些时序跟控制液晶屏输出图像数据时十分类似。

      OV7725传感器输出图像时,一帧帧地输出,在帧内的数据一般从左到右,从上到下, 一个像素一个像素地输出(也可通过寄存器修改方向),见图 [摄像头数据输出](https://doc.embedfire.com/mcu/stm32/f103zhinanzhe/std/zh/latest/book/OV7725.html#id20) 。

      ![摄像头数据输出](记录/OV7725012.jpg)

      例如,见图 [像素同步时序](https://doc.embedfire.com/mcu/stm32/f103zhinanzhe/std/zh/latest/book/OV7725.html#id21) 和图 [QVGA帧图像同步时序](https://doc.embedfire.com/mcu/stm32/f103zhinanzhe/std/zh/latest/book/OV7725.html#qvga) , 若我们使用D2-D9数据线,图像格式设置为RGB565,<img src="记录/image-20220326130518483.png" alt="image-20220326130518483" style="zoom:33%;" />

      ```verilog
      //RGB数据转换为YCBCR
      //因为FPGA处理除法是比较耗费资源的,所以我们团队采用左移右移以替代乘除法。处理整形数据可以将数据整体左移八位,再进行计算,最后在右移回去即可。

      Y’ = 0.257R’ + 0.504G’ + 0.098*B’ + 16
      Cb’ = -0.148R’ - 0.291G’ + 0.439*B’ + 128
      Cr’ = 0.439R’ - 0.368G’ - 0.071*B’ + 128

进行数据输出时,D2-D9数据线在PCLK在上升沿阶段维持稳定, 并且会在1个像素同步时钟PCLK的驱动下发送1字节的数据信号,所以2个PCLK时钟可发送1个RGB565格式的像素数据。 当HREF为高电平时,像素数据依次传输,每传输完一行数据时,行同步信号HREF会输出一个电平跳变信号间隔开当前行和下一行的数据; 一帧的图像由N行数据组成,当VSYNC为低电平时,各行的像素数据依次传输,每传输完一帧图像时,VSYNC会输出一个电平跳变信号。

image-20220326132950059

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30


rows=200
cols=300
num=2000 #随机像素点的个数
img=np.zeros((rows,cols,3),np.uint8)
pos1=np.random.randint(200,size=(num,1)) #行位置随机数组
pos2=np.random.randint(300,size=(num,1)) #列
#随机位置处设置像素点值
for i in range(num):
img[pos1[i],pos2[i],[0]]=np.random.randint(0,255)
img[pos1[i],pos2[i],[1]]=np.random.randint(0,255)
img[pos1[i],pos2[i],[2]]=np.random.randint(0,255)
如果在OpenCV中处理图像,是BGR的顺序。


I=numpy.zeros((3,3),dtype=numpy.uint8)
#图片I大小为3*3,灰度值全为0,也就是黑色图像
I=cv2.cvtColor(I,cv2.COLOR_GRAY2BGR)

#此时图像I变成了三个通道的每个像素点的值都为0
#有27个像素值,HSV色彩空间也是同样的方式,只是只是通道数不同


import cv2
import numpy as np


emptyImage = np.zeros(img.shape, np.uint8)
cv2.imshow("EmptyImage", emptyImage)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69


img = cv2.imread(r'C:\Users\qjy\Desktop\strawberry.jpg')
cv2.imshow("Image", img)

import cv2
import numpy as np
Strawberry=cv2.imread(r'C:\Users\qjy\Desktop\strawberry.jpg')
#Strawberry=cv2.imread("strawberry.jpg")
Lower = np.array([0, 0, 100])
Upper = np.array([40, 40, 255])
Binary = cv2.inRange(Strawberry, Lower, Upper)
cv2.imshow("strawberry", Binary)
cv2.waitKey()
cv2.destroyAllWindows()


fruit = cv2.imread(r'C:\Users\qjy\Desktop\fruits.jpg')
fruit = cv2.cvtColor(fruit,cv2.COLOR_BGR2YUV)
Y,U,V = cv2.split(fruit)
Blueberry = cv2.inRange(U,130,255)
Strawberry = cv2.inRange(V,170,255)
cv2.imshow("blueberry",Blueberry)
cv2.imshow("strawberry",Strawberry)
cv2.waitKey(0)
cv2.destroyAllWindows()



import cv2
img = cv2.imread(r'C:\Users\qjy\Desktop\1.jpg', cv2.IMREAD_COLOR)
ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
(y, cr, cb) = cv2.split(ycrcb)
cr1 = cv2.GaussianBlur(cr, (5, 5), 0)
skin1 = cv2.threshold(cr1, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
cv2.imshow("image CR", cr1)
cv2.imshow("Skin Cr+OSTU", skin1)
cv2.waitKey()
cv2.destroyAllWindows()



import cv2
def cr_otsu1(image):
"""YCrCb颜色空间的Cr分量+Otsu阈值分割
:param image: 图片路径
:return: None
"""

import cv2
img = cv2.imread(r'C:\Users\qjy\Desktop\3.jpg', cv2.IMREAD_COLOR)
ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCR_CB)

(y, cr, cb) = cv2.split(ycrcb)
cr1 = cv2.GaussianBlur(cr, (5, 5), 0)
_, skin = cv2.threshold(cr1, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
cv2.namedWindow("image raw", cv2.WINDOW_NORMAL)
cv2.imshow("image raw", img)
cv2.namedWindow("image CR", cv2.WINDOW_NORMAL)
cv2.imshow("image CR", cr1)
cv2.namedWindow("Skin Cr+OTSU", cv2.WINDOW_NORMAL)
cv2.imshow("Skin Cr+OTSU", skin)

dst = cv2.bitwise_and(img, img, mask=skin)
cv2.namedWindow("seperate", cv2.WINDOW_NORMAL)
cv2.imshow("seperate", dst)
cv2.waitKey()
cv2.destroyAllWindows()

灰度化

转换色彩空间:

python:

1
2
3
4
5
6
7
8
9
#使用Opencv进行色彩空间的转换
import cv2
img = cv2.imread(r'C:\Users\qjy\Desktop\name_of_picture')
img_ycbcr = cv2.cvtColor(img, cv2.COLOR_BGR2YCR_CB)
#img_YUV = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
#img_hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
cv2.imshow("img_of_YCbCr", img_ycbcr)
cv2.waitKey()
cv2.destroyAllWindows()

以RGB格式的彩图为例,通常灰度化采用的方法主要有:

方法1:\(Gray=(R+G+B)/3\)

方法2:\(Gray=max(R,G,B)\)

方法3:\(Gray=0.299R+0.587G+0.114B\)(这种参数考虑到了人眼的生理特点)

所谓阈值处理,就是给定一个阈值,当像素值比指定阈值大或小时做相关的操作。==这个字念yu,不是fa==,方法签名为:cv2.threshold(src,thresh,maxval,type,dst=None),需要将的是OpenCV中提供的几种type:

  • cv2.THRESH_BINARY:若像素值大于阈值,则置为maxval;否则置0
  • cv2.THRESH_BINARY_INV:THRESH_BINARY的反转
  • cv2.THRESH_TRUNC:若像素值大于阈值,则置为阈值;否则不变
  • cv2.THRESH_TOZERO:小于阈值的部分置为0;其他不变
  • cv2.THRESH_TOZERO_INV:THRESH_TOZERO的反转
1
2
3
4
5
_, thresh1 = cv2.threshold(img, 127, 255, type=cv2.THRESH_BINARY)
_, thresh2 = cv2.threshold(img, 127, 255, type=cv2.THRESH_BINARY_INV)
_, thresh3 = cv2.threshold(img, 127, 255, type=cv2.THRESH_TRUNC)
_, thresh4 = cv2.threshold(img, 127, 255, type=cv2.THRESH_TOZERO)
_, thresh5 = cv2.threshold(img, 127, 255, type=cv2.THRESH_TOZERO_INV)
1
2
3
4
5
6
7
8
9
10
11
12
#固定阈值的二值化处理
import cv2
img = cv2.cvtColor(cv2.imread(r'C:\Users\qjy\Desktop\2.jpg'), cv2.COLOR_BGR2GRAY)
threshold_value = 0
while threshold_value < 255:
if threshold_value > 255:
threshold_value = 255
_, thresh1 = cv2.threshold(img, threshold_value, 255, type=cv2.THRESH_BINARY)
cv2.imshow("img_of_YCbCr", thresh1)
cv2.waitKey(0)
cv2.destroyAllWindows()
threshold_value = threshold_value + 10

选则合适的阈值筛选不同的色块,链接下文轮廓识别

1
2
3
4
5
6
7
8
9
10
11
12
#使用Opencv进行色彩空间的转换
import cv2
img = cv2.cvtColor(cv2.imread(r'C:\Users\qjy\Desktop\3.jpg'), cv2.COLOR_BGR2GRAY)
threshold_value = 0
while threshold_value < 255:
if threshold_value > 255:
threshold_value = 255
_, thresh1 = cv2.threshold(img, threshold_value, 255, type=cv2.THRESH_BINARY)
cv2.imshow("img_of_YCbCr", thresh1)
cv2.waitKey(0)
cv2.destroyAllWindows()
threshold_value = threshold_value + 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#自适应阈值二值化处理
import cv2

img = cv2.cvtColor(cv2.imread(r'C:\Users\qjy\Desktop\2.jpg'), cv2.COLOR_BGR2GRAY)
ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
th3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)

cv2.imshow("img_of_YCbCr", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.imshow("img_of_YCbCr", th1)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.imshow("img_of_YCbCr", th2)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.imshow("img_of_YCbCr", th3)
cv2.waitKey(0)
cv2.destroyAllWindows()

腐蚀

1
2
3
4
5
6
7
8
9
10
11
12
img = cv2.imread("i.png")
img_noise = copy.deepcopy(img)
# add some noise to original image
rows, cols = img_noise.shape[:2]
for n in range(50):
i = random.randint(0, rows - 1)
j = random.randint(0, cols - 1)
img_noise[i, j] = 255

kernel = np.ones((5, 5), dtype=np.uint8)
erosion = cv2.erode(img_noise, kernel)
dilation = cv2.dilate(erosion, kernel)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
im = cv2.imread('rectangle.jpg')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)

image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)


print("一共检测到%d个轮廓" % len(contours))
for i in range(len(contours)):
print("第%d个轮廓:" % (i + 1))
print(contours[i])

# -1 代表画出所有轮廓
res = cv2.drawContours(im, contours, -1, (0, 255, 0), thickness=3)

cv2.imshow("img_of_YCbCr", res)
cv2.waitKey(0)
cv2.destroyAllWindows()



import cv2
def find_contours_of_binary_image():
im = cv2.imread(r'C:\Users\qjy\Desktop\2.jpg')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)

image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)


print("一共检测到%d个轮廓" % len(contours))
for i in range(len(contours)):
print("第%d个轮廓:" % (i + 1))
print(contours[i])

# -1 代表画出所有轮廓
res = cv2.drawContours(im, contours, -1, (0, 255, 0), thickness=3)
img_show(res)


轮廓检测能用代码:

1
2
3
4
5
6
7
8
9
10
11
import cv2  
import numpy as np
kernel = np.ones((1, 5), np.uint8)
img = cv2.imread(r'C:\Users\qjy\Desktop\1.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, anchor=(2, 0), iterations=5)
contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,contours,-1,(0,0,255),3)
cv2.imshow("img", img)
cv2.waitKey(0)

能看懂的代码:

1
2
3
4
5
6
7
8
9
#调节二值化的阈值可以描出不同的色块
import cv2
img = cv2.imread(r'C:\Users\qjy\Desktop\3.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,100,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,contours,-1,(0,0,255),3)
cv2.imshow("img", img)
cv2.waitKey(0)

二值化

二值图也就是黑白图。将灰度图转换成黑白图的过程,就是二值化。二值化的一般算法是:

\(g={0,f≤t1,f>t}\)

其中t被称为阀值。阀值的确定方法有下面几种。

Otsu法(大津法或最大类间方差法)

来自霓虹国的大津展之为这种波谷找到了一个合适的数学表达,并于 1979 年发表论文[2]。这个二值化方法称为大津算法(Otsu’s method)

大津算法就是,从 1 到 255 一个个数字试,找到一个数字能够把两个波峰切开,让两个波峰内部的类内方差之和最小。类内方差之和就是,单独求这两个波峰各自的方差,乘上波峰的占比权重,然后加起来。

这个数字就能最好的把图像分开,对应的就是双峰直方图中的波谷。这个算法最多只需遍历两次直方图数组,速度飞快,至今仍被广泛应用。

图像比较复杂的时候,我们对大津算法稍加扩展也可以完成分割。对大津算法的多级推广成为多大津算法(multi Otsu method) [3]

*这里提到的是局部阈值的基本方法,对于实际使用中常见的其他局部阈值方法,请参阅Chow-Kaneko 自适应阈值法 [4]

局部阈值的应用非常广泛,特别是对白纸黑字的处理非常有效。光学字符识别(OCR)和二维码扫描的算法中,很多都用了局部阈值操作。比如下面这张受光不均的二维码。

该算法是一种动态阈值分割算法。它的主要思想是按照灰度特性将图像划分为背景和目标2部分(这里我们将f≤t的部分称为背景,其他部分称为目标。),选取门限值,使得背景和目标之间的方差最大。

注:Nobuyuki Otsu(大津展之),东京大学博士,先后在筑波大学和东京大学担任教授。

其步骤如下:

1.建立图像灰度直方图。

2.计算背景和目标的出现概率。

\(pA=∑i=0tpi,pB=∑i=t+1L−1pi=1−pA\)

其中,A和B分别表示背景部分和目标部分。

3.计算A和B两个区域的类间方差。

\(公式ωA=∑i=0tipipA,ωB=∑i=t+1L−1ipipB(公式1)\)

公式1分别计算A和B区域的平均灰度值;

公式\(ω0=pAωA+pBωB=∑i=0L−1ipi\)(公式2)

公式2计算灰度图像全局的灰度平均值;

公式\(σ2=pA(ωA−ω0)2+pB(ωB−ω0)2\)(公式3)

公式3计算A、B两个区域的类间方差。

4.针对每一个灰度值,计算类间方差。选择方差最大的灰度值,作为阀值t。

下面是几个在使用opencv作轮廓检测时需要注意的点:

  • 为了更精确地提取轮廓,请使用二值图。也就是说,在使用轮廓提取函数前,请将源图片运用阈值进行二值化(cv2.threshold())或者采用Canny边缘检测
  • findContours 函数会修改源图片,如果希望在轮廓检测后继续使用源图片,务必提前保存在另一个变量中。
  • 在OpenCV中,轮廓检测视作从黑色背景中提取白色的物体,所以,在结果中,白色表示物体,黑色表示背景。

提取轮廓的步骤大概会是这样子:

  1. 读取源图片,并转化为灰度图
  2. 运用threshold将灰度图片二值化(也可以使用Canny边缘检测)
  3. 使用findContours()函数找到所有的轮廓
  4. 使用drawContours()函数将轮廓画出来

https://gy23333.github.io/tags/%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86/

图像处理

形态学

膨胀

简介

膨胀是数学形态学的两种基本运算之一,腐蚀是另一种基本运算。它通常应用于二值图像,但也有适用于灰度图像的版本。该算子对二值图像的基本作用是逐渐扩大前景像素(通常为白色像素)区域的边界。因此,前景像素的区域会增大,而这些区域内的孔会变小。

它是如何运作的

这个描述的有用背景在词汇表的数学形态学部分给出。

膨胀运算符接受两段数据作为输入。首先是要放大的图像。第二个是一组坐标点(通常很小),称为结构元素(也称为内核)。正是这个构造元素决定了输入图像的精确膨胀效果。

二值图像膨胀的数学定义如下:

设X为输入二值图像对应的欧几里德坐标集,K为构造元素的坐标集。

Kx表示K的平移使其原点在x处。

那么X乘以K的膨胀就是所有点X的集合使得Kx与X的交点非空。

除了与输入图像相关联的一组坐标的导出方式之外,灰度膨胀的数学定义是相同的。此外,这些坐标是三维的,而不是二维的。

作为二元扩张的一个例子