前言
本系列课程结合软件与硬件,以图像为主要研究对象。课程素材主要借鉴《基于MATLAB与FPGA的图像处理教程》,并将MATLAB改成Python。
一、RGB转YCbCr
RGB转YCbCr有以下几个标准,标准不同,转换公式不同,本文只给出标准3的转换公式。
- 标准清晰度电视(SDTV)
- 清晰度电视(HDTV)
- full range 或者 pc range
full range 或者 pc range公式如下:
[
Y
C
b
C
r
]
=
[
0
128
128
]
+
[
0.299
0.587
0.114
−
0.169
−
0.331
0.500
0.500
−
0.419
−
0.081
]
×
[
R
G
B
]
,
其中
{
R
/
G
/
B
∈
[
0
,
255
]
Y
/
C
b
/
C
r
∈
[
0
,
255
]
(1)
\begin{bmatrix} Y\\ Cb\\ Cr\\ \end{bmatrix} = \begin{bmatrix} 0\\ 128\\ 128\\ \end{bmatrix} + \begin{bmatrix} 0.299&0.587&0.114\\ -0.169&-0.331&0.500\\ 0.500&-0.419&-0.081\\ \end{bmatrix} \times \begin{bmatrix} R\\ G\\ B\\ \end{bmatrix},其中 \begin{cases} R/G/B \in &[0, 255]\\ Y/Cb/Cr \in &[0, 255] \end{cases}\tag{1}
YCbCr
=
0128128
+
0.299−0.1690.5000.587−0.331−0.4190.1140.500−0.081
×
RGB
,其中{R/G/B∈Y/Cb/Cr∈[0,255][0,255](1)
为了方便FPGA硬件设计,需要对公式(1)进行稍微的转变。
Y
=
R
×
0.299
+
G
×
0.587
+
B
×
0.114
(2)
Y = R\times0.299 + G\times0.587+B\times0.114\tag{2}
Y=R×0.299+G×0.587+B×0.114(2)
将公式(2)扩大
2
8
2^8
28=256倍。
256
×
Y
=
R
×
76.544
+
G
×
150.272
+
B
×
29.184
≈
R
×
76
+
G
×
150
+
B
×
29
(3)
256 \times Y = R \times 76.544 + G \times 150.272 + B \times 29.184 \approx R\times76 + G \times150 + B \times 29\tag{3}
256×Y=R×76.544+G×150.272+B×29.184≈R×76+G×150+B×29(3)
结果不能四舍五入,去掉小数即可,算出结果再通过向右移动8位缩小256倍得到Y。Cb,Cr同样的操作,如公式(4)所示。
{
Y
=
(
R
×
77
+
G
×
150
+
B
×
29
)
>
>
8
C
b
=
(
−
R
×
43
−
G
×
84
+
B
×
128
+
32768
)
>
>
8
C
r
=
(
R
×
128
−
G
×
107
−
B
×
20
+
32768
)
>
>
8
(4)
\begin{cases} Y = (R\times77 + G \times150 + B \times 29)>>8\\ Cb = (-R\times43 - G \times84 + B \times 128 + 32768)>>8\\ Cr = (R\times128 - G \times107 - B \times 20 + 32768)>>8\end{cases}\tag{4}
⎩
⎨
⎧Y=(R×77+G×150+B×29)>>8Cb=(−R×43−G×84+B×128+32768)>>8Cr=(R×128−G×107−B×20+32768)>>8(4)
二、Python端代码
import matplotlib.pyplot as plt
import numpy as np
img = plt.imread("lenna.png")#读取图像
h, w, _ = img.shape#获取高宽通道
img_ycbcr = np.zeros(img.shape)
for i in range(h):
for j in range(w):
img_ycbcr[i, j, 0] = (img[i, j, 0] * 76 + img[i, j, 1] * 150 + img[i, j, 2] * 29) / 256
img_ycbcr[i, j, 1] = ((-img[i, j, 0]) * 43 - img[i, j, 1] * 84 + img[i, j, 2] * 128 + 32768) / 256
img_ycbcr[i, j, 2] = (img[i, j, 0] * 128 - img[i, j, 1] * 107 - img[i, j, 2] * 20 + 32768) / 256
#画图
plt.figure(figsize=(10, 10))
plt.subplot(221)
plt.title("RGB image")
plt.imshow(img)
plt.subplot(222)
plt.title("y channel")
plt.imshow(img_ycbcr[:, :, 0], cmap='gray')
plt.subplot(223)
plt.title("cb channel")
plt.imshow(img_ycbcr[:, :, 1], cmap='gray')
plt.subplot(224)
plt.title("cr channel")
plt.imshow(img_ycbcr[:, :, 2], cmap='gray')
三、FPGA端代码
verilog rgb888_to_ycbcr.v代码如下。
module rgb888_to_ycbcr
(
input wire vga_clk ,//vga时钟
input wire sys_rst_n ,//复位信号
input wire [7:0] r ,//原图红色分量
input wire [7:0] g ,//原图绿色分量
input wire [7:0] b ,//原图蓝色分量
output reg [7:0] y ,//ycbcr中的y分量
output reg [15:0] gray_data //vga 565
);
//Y = (77r + 150g + 29b) >> 8
//Cb = (-43r - 85g + 128b)>>8 + 128
//Cr = (128r - 107g - 21b)>>8 + 128
//其他两个分量放上面了
always@(posedge vga_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
y <= 8'd0 ;
else
y <= (77 * r + 150 * g + 29 * b) >> 8 ;//根据公式4
always@(posedge vga_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
gray_data <= 16'd0 ;
else
gray_data <= {y[7:3],y[7:2],y[7:3]} ;//vga是16位,根据565从y分量高位到低位取出进行拼接
endmodule
采集卡采集到FPGA输出的y分量图像。
特意强调一下,由于课本中涉及到仿真,这里就不作仿真的讲解。FPGA端涉及到SDRAM、串口、VGA显示、按键控制、按键消抖、数码管显示、LED灯显示、异步FIFO、图像处理等多项内容。这些内容会在本教程《Python与FPGA》最后进行详细的讲解,目前只讨论图像处理相关代码。
总结
实现的代码还是有所区别,并不是和课本完全一致,但是目标是一致的。就如《基于MATLAB与FPGA的图像处理教程》中说到的,让你的软件起飞。实验结果也是一样,按下按键,图像就被嗖的一下处理掉了,快到只能用xxxx形容。