当前位置:   article > 正文

正点原子imx6ull: QT视频监控项目使用yuyv格式的usb摄像头_qt显示yuv摄像

qt显示yuv摄像

目录

前言

二、修改qt例程

1、添加yuyv转rgb的函数到capture_thread.cpp

2、声明屏幕缓冲变量

3、yuyv转rgb的实际处理

 三、测试

    1、开发板获取摄像头数据测试

    2、客户端与服务器通信测试

四、修改后的正点原子video_server项目代码


前言

正点原子《I.MX6U 嵌入式 Qt 开发指南》教程使用的是ov系列的摄像头,输出rgb格式,可以直接显示到屏幕,不需要进行格式转化。由于我使用的是yuyv格式的usb摄像头,下面进行适配。

参考:正点原子《I.MX6U 嵌入式 Qt 开发指南》第二十八章 视频监控项目

lcd大小:4.3寸 480*272

QT文件:  server: video_server     client: video_client

一、YUYV摄像头直接使用原始例程

先查看usb摄像头的输出像素格式:v4l2-ctl -d /dev/video1 --all

直接修改正点原子的例程,修改pixformat=V4L2_PIX_FMT_YUYV

 

此时使用例程,显示效果如下,很显然原因是摄像头采集的yuyv数据,以Format_RGB16显示到QImage控件上导致了格式异常。

二、修改qt例程

1、添加yuyv转rgb的函数到capture_thread.cpp

  1. void yuyv_to_rgb(unsigned char* yuv,unsigned char* rgb)
  2. {
  3. unsigned int i;
  4. unsigned char* y0 = yuv + 0;
  5. unsigned char* u0 = yuv + 1;
  6. unsigned char* y1 = yuv + 2;
  7. unsigned char* v0 = yuv + 3;
  8. unsigned char* r0 = rgb + 0;
  9. unsigned char* g0 = rgb + 1;
  10. unsigned char* b0 = rgb + 2;
  11. unsigned char* r1 = rgb + 3;
  12. unsigned char* g1 = rgb + 4;
  13. unsigned char* b1 = rgb + 5;
  14. float rt0 = 0, gt0 = 0, bt0 = 0, rt1 = 0, gt1 = 0, bt1 = 0;
  15. for(i = 0; i <= (WIDTH * HEIGHT) / 2 ;i++)
  16. {
  17. bt0 = 1.164 * (*y0 - 16) + 2.018 * (*u0 - 128);
  18. gt0 = 1.164 * (*y0 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);
  19. rt0 = 1.164 * (*y0 - 16) + 1.596 * (*v0 - 128);
  20. bt1 = 1.164 * (*y1 - 16) + 2.018 * (*u0 - 128);
  21. gt1 = 1.164 * (*y1 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);
  22. rt1 = 1.164 * (*y1 - 16) + 1.596 * (*v0 - 128);
  23. if(rt0 > 250) rt0 = 255;
  24. if(rt0< 0) rt0 = 0;
  25. if(gt0 > 250) gt0 = 255;
  26. if(gt0 < 0) gt0 = 0;
  27. if(bt0 > 250) bt0 = 255;
  28. if(bt0 < 0) bt0 = 0;
  29. if(rt1 > 250) rt1 = 255;
  30. if(rt1 < 0) rt1 = 0;
  31. if(gt1 > 250) gt1 = 255;
  32. if(gt1 < 0) gt1 = 0;
  33. if(bt1 > 250) bt1 = 255;
  34. if(bt1 < 0) bt1 = 0;
  35. *r0 = (unsigned char)rt0;
  36. *g0 = (unsigned char)gt0;
  37. *b0 = (unsigned char)bt0;
  38. *r1 = (unsigned char)rt1;
  39. *g1 = (unsigned char)gt1;
  40. *b1 = (unsigned char)bt1;
  41. yuv = yuv + 4;
  42. rgb = rgb + 6;
  43. if(yuv == NULL)
  44. break;
  45. y0 = yuv;
  46. u0 = yuv + 1;
  47. y1 = yuv + 2;
  48. v0 = yuv + 3;
  49. r0 = rgb + 0;
  50. g0 = rgb + 1;
  51. b0 = rgb + 2;
  52. r1 = rgb + 3;
  53. g1 = rgb + 4;
  54. b1 = rgb + 5;
  55. }
  56. }

2、声明屏幕缓冲变量

这里的WIDTH和HEIGHT需要根据实际显示设置,由于我的lcd为480*272,因此我设置视频数据为320*240

  1. #define WIDTH     320
  2. #define HEIGHT    240
  3. unsigned char src_buffer[WIDTH*HEIGHT*2];        //用来装v4l2出队的yuyv数据
  4. unsigned char rgb_buffer[WIDTH*HEIGHT*3];        //用来装yuyv转换为rgb的数据

3、yuyv转rgb的实际处理

在帧缓冲出队函数中,对帧缓冲数据进行memcpy到src_buffer,再调用yuyv_to_rgb函数,将转换为rgb的数据保存到rgb_buffer中。然后将rgb_buffer显示到屏幕。

 三、测试

    1、开发板获取摄像头数据测试

    将QT工程video_server交叉编译后,放到开发板上运行,查看显示到本地显示的数据是否正常。此时与一开始相比,颜色显示已经正常。

    2、客户端与服务器通信测试

    将QT工程video_client编译后在pc上运行。

    将开发板上的video_server勾选开启广播后,运行,即可看到客户端收到的视频数据。

四、修改后的正点原子video_server项目代码

  1. /******************************************************************
  2. Copyright © Deng Zhimao Co., Ltd. 2021-2030. All rights reserved.
  3. * @projectName video_server
  4. * @brief mainwindow.cpp
  5. * @author Deng Zhimao
  6. * @email dengzhimao@alientek.com
  7. * @link www.openedv.com
  8. * @date 2021-11-19
  9. *******************************************************************/
  10. #include "mainwindow.h"
  11. MainWindow::MainWindow(QWidget *parent)
  12. : QMainWindow(parent)
  13. {
  14. this->setGeometry(0, 0, 480 , 272); //由于屏幕大小不同,进行了修改
  15. videoLabel = new QLabel(this);
  16. videoLabel->setText("未获取到图像数据或未开启本地显示");
  17. videoLabel->setStyleSheet("QWidget {color: white;}");
  18. videoLabel->setAlignment(Qt::AlignCenter);
  19. videoLabel->resize(480, 272); //由于屏幕大小不同,进行了修改
  20. checkBox1 = new QCheckBox(this);
  21. checkBox2 = new QCheckBox(this);
  22. checkBox1->resize(120, 50);
  23. checkBox2->resize(120, 50);
  24. checkBox1->setText("本地显示");
  25. checkBox2->setText("开启广播");
  26. checkBox1->setStyleSheet("QCheckBox {color: yellow;}"
  27. "QCheckBox:indicator {width: 40; height: 40;}");
  28. checkBox2->setStyleSheet("QCheckBox {color: yellow;}"
  29. "QCheckBox:indicator {width: 40; height: 40}");
  30. /* 按钮 */
  31. startCaptureButton = new QPushButton(this);
  32. startCaptureButton->setCheckable(true);
  33. startCaptureButton->setText("开始采集摄像头数据");
  34. /* 设置背景颜色为黑色 */
  35. QColor color = QColor(Qt::black);
  36. QPalette p;
  37. p.setColor(QPalette::Window, color);
  38. this->setPalette(p);
  39. /* 样式表 */
  40. startCaptureButton->setStyleSheet("QPushButton {background-color: white; border-radius: 30}"
  41. "QPushButton:pressed {background-color: red;}");
  42. captureThread = new CaptureThread(this);
  43. connect(startCaptureButton, SIGNAL(clicked(bool)), captureThread, SLOT(setThreadStart(bool)));
  44. connect(startCaptureButton, SIGNAL(clicked(bool)), this, SLOT(startCaptureButtonClicked(bool)));
  45. connect(captureThread, SIGNAL(imageReady(QImage)), this, SLOT(showImage(QImage)));
  46. connect(checkBox1, SIGNAL(clicked(bool)), captureThread, SLOT(setLocalDisplay(bool)));
  47. connect(checkBox2, SIGNAL(clicked(bool)), captureThread, SLOT(setBroadcast(bool)));
  48. }
  49. MainWindow::~MainWindow()
  50. {
  51. }
  52. void MainWindow::showImage(QImage image)
  53. {
  54. videoLabel->setPixmap(QPixmap::fromImage(image));
  55. }
  56. void MainWindow::resizeEvent(QResizeEvent *event)
  57. {
  58. Q_UNUSED(event)
  59. startCaptureButton->move((this->width() - 200) / 2, this->height() - 80);
  60. startCaptureButton->resize(200, 60);
  61. videoLabel->move((this->width() - 480) / 2, (this->height() - 272) / 2); //由于屏幕大小不同,进行了修改
  62. checkBox1->move(this->width() - 120, this->height() / 2 - 50);
  63. checkBox2->move(this->width() - 120, this->height() / 2 + 25);
  64. }
  65. void MainWindow::startCaptureButtonClicked(bool start)
  66. {
  67. if (start)
  68. startCaptureButton->setText("停止采集摄像头数据");
  69. else
  70. startCaptureButton->setText("开始采集摄像头数据");
  71. }

 

  1. /******************************************************************
  2. Copyright © Deng Zhimao Co., Ltd. 2021-2030. All rights reserved.
  3. * @projectName video_server
  4. * @brief capture_thread.cpp
  5. * @author Deng Zhimao
  6. * @email dengzhimao@alientek.com
  7. * @link www.openedv.com
  8. * @date 2021-11-19
  9. *******************************************************************/
  10. #include "capture_thread.h"
  11. #include <string.h>
  12. #define WIDTH 320 //摄像头拍摄以及显示的大小
  13. #define HEIGHT 240
  14. void yuyv_to_rgb(unsigned char* yuv,unsigned char* rgb)
  15. {
  16. unsigned int i;
  17. unsigned char* y0 = yuv + 0;
  18. unsigned char* u0 = yuv + 1;
  19. unsigned char* y1 = yuv + 2;
  20. unsigned char* v0 = yuv + 3;
  21. unsigned char* r0 = rgb + 0;
  22. unsigned char* g0 = rgb + 1;
  23. unsigned char* b0 = rgb + 2;
  24. unsigned char* r1 = rgb + 3;
  25. unsigned char* g1 = rgb + 4;
  26. unsigned char* b1 = rgb + 5;
  27. float rt0 = 0, gt0 = 0, bt0 = 0, rt1 = 0, gt1 = 0, bt1 = 0;
  28. for(i = 0; i <= (WIDTH * HEIGHT) / 2 ;i++)
  29. {
  30. bt0 = 1.164 * (*y0 - 16) + 2.018 * (*u0 - 128);
  31. gt0 = 1.164 * (*y0 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);
  32. rt0 = 1.164 * (*y0 - 16) + 1.596 * (*v0 - 128);
  33. bt1 = 1.164 * (*y1 - 16) + 2.018 * (*u0 - 128);
  34. gt1 = 1.164 * (*y1 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);
  35. rt1 = 1.164 * (*y1 - 16) + 1.596 * (*v0 - 128);
  36. if(rt0 > 250) rt0 = 255;
  37. if(rt0< 0) rt0 = 0;
  38. if(gt0 > 250) gt0 = 255;
  39. if(gt0 < 0) gt0 = 0;
  40. if(bt0 > 250) bt0 = 255;
  41. if(bt0 < 0) bt0 = 0;
  42. if(rt1 > 250) rt1 = 255;
  43. if(rt1 < 0) rt1 = 0;
  44. if(gt1 > 250) gt1 = 255;
  45. if(gt1 < 0) gt1 = 0;
  46. if(bt1 > 250) bt1 = 255;
  47. if(bt1 < 0) bt1 = 0;
  48. *r0 = (unsigned char)rt0;
  49. *g0 = (unsigned char)gt0;
  50. *b0 = (unsigned char)bt0;
  51. *r1 = (unsigned char)rt1;
  52. *g1 = (unsigned char)gt1;
  53. *b1 = (unsigned char)bt1;
  54. yuv = yuv + 4;
  55. rgb = rgb + 6;
  56. if(yuv == NULL)
  57. break;
  58. y0 = yuv;
  59. u0 = yuv + 1;
  60. y1 = yuv + 2;
  61. v0 = yuv + 3;
  62. r0 = rgb + 0;
  63. g0 = rgb + 1;
  64. b0 = rgb + 2;
  65. r1 = rgb + 3;
  66. g1 = rgb + 4;
  67. b1 = rgb + 5;
  68. }
  69. }
  70. void CaptureThread::run()
  71. {
  72. /* 下面的代码请参考正点原子C应用编程V4L2章节,摄像头编程,这里不作解释 */
  73. #ifdef linux
  74. #ifndef __arm__
  75. return;
  76. #endif
  77. int video_fd = -1;
  78. struct v4l2_format fmt;
  79. struct v4l2_requestbuffers req_bufs;
  80. static struct v4l2_buffer buf;
  81. int n_buf;
  82. struct buffer_info bufs_info[VIDEO_BUFFER_COUNT];
  83. enum v4l2_buf_type type;
  84. unsigned char src_buffer[WIDTH*HEIGHT*2];
  85. unsigned char rgb_buffer[WIDTH*HEIGHT*2];
  86. video_fd = open(VIDEO_DEV, O_RDWR);
  87. if (0 > video_fd) {
  88. printf("ERROR: failed to open video device %s\n", VIDEO_DEV);
  89. return ;
  90. }
  91. fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  92. fmt.fmt.pix.width = WIDTH;
  93. fmt.fmt.pix.height = HEIGHT;
  94. fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
  95. fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
  96. if (0 > ioctl(video_fd, VIDIOC_S_FMT, &fmt)) {
  97. printf("ERROR: failed to VIDIOC_S_FMT\n");
  98. close(video_fd);
  99. return ;
  100. }
  101. if (0 > ioctl(video_fd, VIDIOC_G_FMT, &fmt)) {
  102. printf("ERROR: failed to VIDIOC_S_FMT\n");
  103. close(video_fd);
  104. return ;
  105. }
  106. printf("fmt.type:\t\t%d\n",fmt.type);
  107. printf("pix.pixelformat:\t%c%c%c%c\n", \
  108. fmt.fmt.pix.pixelformat & 0xFF,\
  109. (fmt.fmt.pix.pixelformat >> 8) & 0xFF, \
  110. (fmt.fmt.pix.pixelformat >> 16) & 0xFF,\
  111. (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
  112. printf("pix.width:\t\t%d\n",fmt.fmt.pix.width);
  113. printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);
  114. printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);
  115. req_bufs.count = VIDEO_BUFFER_COUNT;
  116. req_bufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  117. req_bufs.memory = V4L2_MEMORY_MMAP;
  118. if (0 > ioctl(video_fd, VIDIOC_REQBUFS, &req_bufs)) {
  119. printf("ERROR: failed to VIDIOC_REQBUFS\n");
  120. return ;
  121. }
  122. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  123. buf.memory = V4L2_MEMORY_MMAP;
  124. for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++) {
  125. buf.index = n_buf;
  126. if (0 > ioctl(video_fd, VIDIOC_QUERYBUF, &buf)) {
  127. printf("ERROR: failed to VIDIOC_QUERYBUF\n");
  128. return ;
  129. }
  130. bufs_info[n_buf].length = buf.length;
  131. bufs_info[n_buf].start = mmap(NULL, buf.length,
  132. PROT_READ | PROT_WRITE, MAP_SHARED,
  133. video_fd, buf.m.offset);
  134. if (MAP_FAILED == bufs_info[n_buf].start) {
  135. printf("ERROR: failed to mmap video buffer, size 0x%x\n", buf.length);
  136. return ;
  137. }
  138. }
  139. for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++) {
  140. buf.index = n_buf;
  141. if (0 > ioctl(video_fd, VIDIOC_QBUF, &buf)) {
  142. printf("ERROR: failed to VIDIOC_QBUF\n");
  143. return ;
  144. }
  145. }
  146. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  147. if (0 > ioctl(video_fd, VIDIOC_STREAMON, &type)) {
  148. printf("ERROR: failed to VIDIOC_STREAMON\n");
  149. return ;
  150. }
  151. while (startFlag) {
  152. for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++) {
  153. buf.index = n_buf;
  154. //出队
  155. if (0 > ioctl(video_fd, VIDIOC_DQBUF, &buf)) {
  156. printf("ERROR: failed to VIDIOC_DQBUF\n");
  157. return;
  158. }
  159. // printf("now memcpy the src buf, buf start=%d, len=%d\r\n", bufs_info[n_buf].start, bufs_info[n_buf].length);
  160. memcpy(src_buffer, bufs_info[n_buf].start, bufs_info[n_buf].length); //将帧缓冲数据复制到到src_buffer
  161. yuyv_to_rgb((unsigned char*)bufs_info[n_buf].start, rgb_buffer); //yuyv转rgb
  162. QImage qImage(rgb_buffer, fmt.fmt.pix.width, fmt.fmt.pix.height, QImage::Format_RGB16); //显示到屏幕
  163. // QImage qImage((unsigned char*)bufs_info[n_buf].start, fmt.fmt.pix.width, fmt.fmt.pix.height, QImage::Format_RGB16);
  164. /* 是否开启本地显示,开启本地显示可能会导致开启广播卡顿,它们互相制约 */
  165. if (startLocalDisplay)
  166. emit imageReady(qImage);
  167. /* 是否开启广播,开启广播会导致本地显示卡顿,它们互相制约 */
  168. if (startBroadcast) {
  169. /* udp套接字 */
  170. QUdpSocket udpSocket;
  171. /* QByteArray类型 */
  172. QByteArray byte;
  173. /* 建立一个用于IO读写的缓冲区 */
  174. QBuffer buff(&byte);
  175. /* image转为byte的类型,再存入buff */
  176. qImage.save(&buff, "JPEG", -1);
  177. /* 转换为base64Byte类型 */
  178. QByteArray base64Byte = byte.toBase64();
  179. /* 由udpSocket以广播的形式传输数据,端口号为8888 */
  180. udpSocket.writeDatagram(base64Byte.data(), base64Byte.size(), QHostAddress::Broadcast, 8888);
  181. }
  182. //入队
  183. if (0 > ioctl(video_fd, VIDIOC_QBUF, &buf)) {
  184. printf("ERROR: failed to VIDIOC_QBUF\n");
  185. return;
  186. }
  187. }
  188. }
  189. msleep(800);//at lease 650
  190. for (int i = 0; i < VIDEO_BUFFER_COUNT; i++) {
  191. munmap(bufs_info[i].start, buf.length);
  192. }
  193. close(video_fd);
  194. #endif
  195. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/711338
推荐阅读
相关标签
  

闽ICP备14008679号