使用UDP传输opencv的mat数据并显示
本教程适用于进阶的小白尝试
先说一下背景吧,正在工作的我,突然间看到淘宝上有个很漂亮的价格还不错的linux小板子,遂买下。没错,工作太无聊以至于开始摸鱼学习~
但奈何每天工作完回家就像躺着,所以板子到手都快半年了才开始研究
实现了简陋的摄像头传输,所以如果有大佬们有更优的代码请评论区讨论,必有3Q回复~
话不多说请看效果图
器材准备
硬件:
1.Luckfox Pico小板一个
2.Luckfox Pico配套摄像头一个
3.一个小TF卡(我用的是16G的)
软件:
1.带ubuntu的虚拟机
2.ubuntu安装opencv
3.win下的终端我用的MobaXterm
4.luckfox官方教程网页
小板子我是看了价格便宜买的,才四十,感觉linux的学习成本越来越低喽
摄像头五十拿下
基础说明
0.需要将tf卡按照官方教程烧录好
1.此教程是基于Luckfox Pico的 “优质社区分享”-----“opencv-mobile” 篇章做的延申,所以可以先将opencv-mobile篇章做完,这样可以确定摄像头到开发板到电脑的链路是通的。luckfox的opencv-mobile篇章
ubuntu
2.虚拟机中的opencv需要安装好,以便ubuntu调用显示 opencv安装教程及说明
3.还会用到利用opencv的编译 opencv的cmake最简单编译
以上四步完成后,下面的操作简直就易如反掌了
win与虚拟机网络设置
我当前的做法是将开发板连接到win电脑,然后通过虚拟机的桥接模式连接到虚拟机,做法如下
WIN连接
其实官方有说明了,不过为了方便大家我还是列出来吧
开发板本身的地址是172.32.0.93
开发板通过typec连接到电脑后电脑会自动多出一个网络连接
名称是“Remote NDIS based Internet Sharing Device”
将此网络的IPV4的ID改成172.32.0.100,这是为了让电脑与开发板在同一网段下可以相互访问
至此,便可以使用MobaXterm连接至开发板
用户名:root
密码:luckfox
虚拟机连接
首先打开虚拟网络编辑器
将桥接模式的网络设置为开发板的网络(别忘应用哦)
其次将虚拟机的设置指定为刚设置好的桥接
然后在ubuntu系统中将网络地址手动设置为172.32.0.101(其实和win下类似,不过是防止与win冲突)
至此网络设置完美落幕
开发板程序
直接基于官方的luckfox的opencv-mobile篇章内的代码稍作改动(这也是为什么建议先将官方的opencv例子跑通)
拓展:需要注意的是,官方的opencv精简,所以无法使用此opencv库来发送已有的图片,因为官方的opencv精简到无法读取图片哈哈哈哈
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h> // sleep()
#define SERVER_IP "172.32.0.101" // 服务器IP地址,这里使用本地地址作为示例
#define SERVER_PORT 90 // 服务器端口号,根据你的需要设置
int main()
{
cv::VideoCapture cap;
cap.set(cv::CAP_PROP_FRAME_WIDTH, 320);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 240);
cap.open(0);
// cv::Mat images = cv::imread("123.jpg");
// if(images.empty()){
// printf("oprn .jpg error!\n");
// return -1;
// }
const int w = cap.get(cv::CAP_PROP_FRAME_WIDTH);
const int h = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
fprintf(stderr, "%d x %d\n", w, h);
cv::Mat bgr;
cap >> bgr;
sleep(1);
cap.release();
cv::Mat out(h , w , CV_8UC3);
bgr.copyTo(out(cv::Rect(0, 0, w, h)));
cv::imwrite("out.jpg", out);
#if 1
int sockfd;
struct sockaddr_in server_addr;
char *message = "Hello, UDP Server!"; // 要发送的消息内容
int bytes_sent, bytes_received;
// 创建UDP socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
printf("Socket created successfully.\n");
// 设置服务器地址信息
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET; // 使用IPv4地址族
server_addr.sin_port = htons(SERVER_PORT); // 设置端口号(网络字节序)
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); // 设置服务器IP地址
// 将图片编码为JPG格式
std::vector<uchar> buf;
cv::imencode(".jpg", bgr, buf);
uint32_t size_buf = buf.size();
// 发送数据到服务器
bytes_sent = sendto(sockfd, &size_buf, sizeof(buf.size()), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (bytes_sent < 0) {
perror("Sendto failed");
exit(EXIT_FAILURE);
}
printf("Data sent to server successfully.\n");
sleep(1);
bytes_sent = sendto(sockfd, buf.data(), buf.size(), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (bytes_sent < 0) {
perror("Sendto failed");
exit(EXIT_FAILURE);
}
printf("Data sent to server successfully.\n");
// 这里可以添加接收服务器响应的代码(使用recvfrom函数)...(略)...
// ...处理接收到的数据...(略)...
close(sockfd); // 关闭socket连接
#endif
return 0;
}
ubuntu程序
这是我的ubuntu的程序,大概意思就是先接收图片的大小,然后再按照大小接收图片的数据。
备注:有时候接收的大小也会乱掉,大家多跑几次就通了哈哈哈,毕竟不是精装房~
先看我的代码结构,因为这也是用cmake来做的编译
然后是cmakelists的代码,其实套路就是 opencv的cmake最简单编译
project(test)
cmake_minimum_required(VERSION 3.5)
set(CMAKE_CXX_STANDARD 11)
SET(CMAKE_C_COMPILER "g++")
SET(CMAKE_CXX_COMPILER "g++")
#set(OpenCV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/usr/lib/x86_64-linux-gnu/cmake/opencv4")
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(test opencv.cpp)
target_link_libraries(test ${OpenCV_LIBS})
以下才是真正的代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#define BUFFER_SIZE (1024 * 16)
#define PORT 90
int main() {
int sockfd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
int received_bytes;
// 创建socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定socket到服务器地址
if (bind(sockfd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
printf("Waiting for data on port %d\n", PORT);
uint32_t len = 0;
// 接收数据
while (1) {
while(1)
{
memset(buffer, 0, 4);
received_bytes = recvfrom(sockfd, buffer, 4, 0, NULL, NULL);
if (received_bytes < 0) {
perror("recvfrom failed");
exit(EXIT_FAILURE);
}
if((buffer[1]<0x40)&&(buffer[1]>0x10))
{
if((buffer[2]==0x00)&&(buffer[3]==0x00))
{
len = ((uint32_t)buffer[3]<<24)|((uint32_t)buffer[2]<<16)|((uint32_t)buffer[1]<<8)|((uint32_t)buffer[0]);
printf("Received length: %x\n", len);
break;
}
else
{
printf("buffer 2-3 is not 0x00 ! %x %x %x %x\n",buffer[0],buffer[1],buffer[2],buffer[3]);
}
}
else
{
printf("buffer 0-1 is not 0x20-0x25 ! %x %x %x %x\n",buffer[0],buffer[1],buffer[2],buffer[3]);
}
}
memset(buffer, 0, len);
received_bytes = recvfrom(sockfd, buffer, len, 0, NULL, NULL);
if (received_bytes < 0) {
perror("recvfrom failed");
exit(EXIT_FAILURE);
}
printf("Received message!\n");
cv::Mat image = cv::imdecode(cv::Mat(1, received_bytes, CV_8UC1, buffer), cv::IMREAD_COLOR);
printf("Received image!\n");
if (!image.empty()) {
// 显示或处理图像
cv::imshow("Received Image", image);
cv::waitKey(0);
} else {
printf("Failed to decode image\n");
}
}
close(sockfd);
return 0;
}
效果
先运行虚拟机上build里的test:
sudo ./test
然后运行开发板上已经给过去的opencv-mobile-test
./opencv-mobile-test
然后就会弹出照片框喽