ubuntu下使用docker、socket、yolov5进行图像处理数据交互记录
概述:主要实现了在宿主机上通过8000端口传递一张图像给docker镜像,然后镜像中处理后,通过8001端口回传处理后的图像给宿主机。
第一章、构建镜像
一、dockerfile文件
1.拉取ubuntu20.04镜像
基于ubuntu20.04进行构建,拉取ubuntu20.04的指令如下:
sudo docker pull ubuntu:20.04
2.编写dockerfile文件
dockerfile写入如下:
# Dockerfile
# Base images 基础镜像
FROM ubuntu:20.04
# MAINTAINER 维护者信息
maintainer chenjun_1241370589@qq.com
# 设置超时
ENV PIP_DEFAULT_TIMEOUT=100
# 设置环境变量以避免手动选择时区
ENV DEBIAN_FRONTEND=noninteractive
#RUN 执行以下命令
RUN apt update
RUN apt install python3 python3-pip -y
RUN apt-get install nano
# 安装必要的系统依赖项
RUN apt-get update && apt-get install -y \
libglib2.0-0 \
libsm6 \
libxext6 \
libxrender-dev
RUN pip3 install --upgrade pip setuptools
RUN pip3 cache purge
RUN pip3 install opencv-python==4.5.5.62 -i https://pypi.tuna.tsinghua.edu.cn/simple
# 安装 OpenCV
RUN apt-get update && apt-get install -y \
libgl1-mesa-glx # 安装 OpenGL 库
# 其他依赖和设置
RUN pip3 install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN apt install libgl1-mesa-glx
RUN mkdir -p /data/code/
#拷贝文件至工作文件夹
COPY test.py /data/code/test.py
#工作目录
WORKDIR /data/code/
#容器启动时执行的命令
CMD ["python3","test.py"]
二、编写docker内运行文件
1.新建test.py文件
test.py文件将被拷贝进镜像中,作为镜像内部运行文件。写入如下:
import cv2 # 导入OpenCV库
import numpy as np # 导入NumPy库
import socket # 导入socket库
import struct # 导入struct库
import time
def receive_image():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建TCP/IP套接字
server_socket.bind(('0.0.0.0', 8000)) # 绑定到本地地址和端口8000
server_socket.listen(1) # 监听连接
print("Waiting for connection...") # 打印等待连接信息
client_socket, addr = server_socket.accept() # 接受连接
print(f"Connected to {addr}") # 打印连接地址
data = b"" # 初始化数据变量
payload_size = struct.calcsize("L") # 获取消息大小的字节数
while len(data) < payload_size: # 接收消息大小
data += client_socket.recv(4096)
packed_msg_size = data[:payload_size] # 获取打包的消息大小
data = data[payload_size:] # 剩余数据
msg_size = struct.unpack("L", packed_msg_size)[0] # 解包消息大小
while len(data) < msg_size: # 接收完整消息
data += client_socket.recv(4096)
frame_data = data[:msg_size] # 获取图像数据
frame = cv2.imdecode(np.frombuffer(frame_data, dtype=np.uint8), cv2.IMREAD_COLOR) # 解码图像
client_socket.close() # 关闭客户端套接字
server_socket.close() # 关闭服务器套接字
return frame # 返回图像
def process_image(image):
height, width = image.shape[:2] # 获取图像高度和宽度
new_height, new_width = height // 2, width // 2 # 计算新高度和宽度
print("Process_image ok !")
return cv2.resize(image, (new_width, new_height)) # 调整图像大小
def send_image(image):
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建TCP/IP套接字
for _ in range(5): # 尝试5次连接
try:
client_socket.connect(('172.17.0.1', 8001)) # 连接到指定地址和端口
break
except ConnectionRefusedError:
print("Connection refused, retrying...")
time.sleep(2) # 等待2秒后重试
else:
print("Failed to connect after 5 attempts")
return
_, img_encoded = cv2.imencode('.jpg', image) # 编码图像为JPEG格式
data = img_encoded.tobytes() # 转换为字节数据
client_socket.sendall(struct.pack("L", len(data)) + data) # 发送数据
client_socket.close() # 关闭客户端套接字
if __name__ == "__main__":
while True:
# 接收图像
received_image = receive_image()
# 处理图像
processed_image = process_image(received_image)
# 发送处理后的图像
send_image(processed_image)
其中client_socket.connect((‘172.17.0.1’, 8001)) # 连接到指定地址和端口这一行中的ip需要在宿主机运行指令:
ip addr show docker0
将inet后的ip填入代码中。
三、构建镜像
宿主机终端输入:
sudo docker build -t python_socket2 . -f Dockerfile
其中“python_socket2”是我自己创建的名字,自行修改。
第二章、宿主机代码
一、交互代码
在宿主机中写入test1.py代码,这里图像处理部分只对图像进行尺寸减半的操作,代码如下:
import cv2
import numpy as np
import socket
import struct
def send_image(image_path):
# 读取图像
image = cv2.imread(image_path)
if image is None:
print(f"无法读取图像: {image_path}")
return
# 创建TCP/IP套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 8000)) # 连接到Docker容器的8000端口
# 编码图像为JPEG格式
_, img_encoded = cv2.imencode('.jpg', image)
data = img_encoded.tobytes()
# 发送数据
client_socket.sendall(struct.pack("L", len(data)) + data)
client_socket.close()
print("图像已发送")
def receive_processed_image():
# 创建TCP/IP套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 8001)) # 绑定到本地地址和端口8001
server_socket.listen(1)
print("等待接收处理后的图像...")
client_socket, addr = server_socket.accept()
print(f"已连接到 {addr}")
data = b""
payload_size = struct.calcsize("L")
while len(data) < payload_size:
data += client_socket.recv(4096)
packed_msg_size = data[:payload_size]
data = data[payload_size:]
msg_size = struct.unpack("L", packed_msg_size)[0]
while len(data) < msg_size:
data += client_socket.recv(4096)
frame_data = data[:msg_size]
# 解码图像
frame = cv2.imdecode(np.frombuffer(frame_data, dtype=np.uint8), cv2.IMREAD_COLOR)
client_socket.close()
server_socket.close()
return frame
if __name__ == "__main__":
image_path = "/home/cj/work/Docker/Yolov5DnnImage/bus.jpg" # 替换为实际的图像路径
send_image(image_path)
processed_image = receive_processed_image()
# 显示处理后的图像
#cv2.imshow("Processed Image", processed_image)
#cv2.waitKey(0)
#cv2.destroyAllWindows()
# 保存处理后的图像
cv2.imwrite("processed_image.jpg", processed_image)
print("处理后的图像已保存为 processed_image.jpg")
图像输入地址自行修改。
第三章、交互使用
一、docker端
在宿主机终端输入:
sudo docker run -p 8000:8000 -i -t python_socket2:latest
这里的“python_socket2:latest”修改成自己的镜像。
查看镜像宿主机终端输入:
sudo docker images
二、宿主机端
运行test1.py文件
同时在docker端也会有输出:
在宿主机当前工作目录下也会生成对应的图像。
第四章、增加yolov5目标检测
一、宿主机文件摆放
因为需要更新dockerfile文件,因此需要留意文件摆放。本文中将需要用的文件都摆放在项目目录下。
目录中,bus.jpg是等待传输和处理的图像,classes.txt是目标检测类别文件,Dockerfile是进行构建文件,processed_image.jpg是宿主机传递给镜像处理后传输回来的图像,test.py是镜像中的主函数代码,test1.py是宿主机端的主函数代码,yolo.py是yolov5调用opencv的dnn模块进行推理的代码,yolov5s.onnx是模型全重文件。
二、各文件内容
1.classes.txt文件
person
bicycle
car
motorbike
aeroplane
bus
train
truck
boat
traffic light
fire hydrant
stop sign
parking meter
bench
bird
cat
dog
horse
sheep
cow
elephant
bear
zebra
giraffe
backpack
umbrella
handbag
tie
suitcase
frisbee
skis
snowboard
sports ball
kite
baseball bat
baseball glove
skateboard
surfboard
tennis racket
bottle
wine glass
cup
fork
knife
spoon
bowl
banana
apple
sandwich
orange
broccoli
carrot
hot dog
pizza
donut
cake
chair
sofa
pottedplant
bed
diningtable
toilet
tvmonitor
laptop
mouse
remote
keyboard
cell phone
microwave
oven
toaster
sink
refrigerator
book
clock
vase
scissors
teddy bear
hair drier
toothbrush
2.Dockerfile文件
主要是通过COPY命令,将一些必要的文件拷贝到镜像中
# Dockerfile
# Base images 基础镜像
FROM ubuntu:20.04
# MAINTAINER 维护者信息
maintainer chenjun_1241370589@qq.com
# 设置超时
ENV PIP_DEFAULT_TIMEOUT=100
# 设置环境变量以避免手动选择时区
ENV DEBIAN_FRONTEND=noninteractive
#RUN 执行以下命令
RUN apt update
RUN apt install python3 python3-pip -y
RUN apt-get install nano
# 安装必要的系统依赖项
RUN apt-get update && apt-get install -y \
libglib2.0-0 \
libsm6 \
libxext6 \
libxrender-dev
RUN pip3 install --upgrade pip setuptools
RUN pip3 cache purge
RUN pip3 install opencv-python==4.5.5.62 -i https://pypi.tuna.tsinghua.edu.cn/simple
# 安装 OpenCV
RUN apt-get update && apt-get install -y \
libgl1-mesa-glx # 安装 OpenGL 库
# 其他依赖和设置
RUN pip3 install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN apt install libgl1-mesa-glx
RUN mkdir -p /data/code/
#拷贝文件至工作文件夹
COPY test.py /data/code/test.py
COPY yolo.py /data/code/yolo.py
COPY classes.txt /data/code/classes.txt
COPY yolov5s.onnx /data/code/yolov5s.onnx
#工作目录
WORKDIR /data/code/
#容器启动时执行的命令
CMD ["python3","test.py"]
3.test.py文件
主要增加Yolov5Dnn函数
import cv2 # 导入OpenCV库
import numpy as np # 导入NumPy库
import socket # 导入socket库
import struct # 导入struct库
import time
from yolo import build_model,detect,load_classes,wrap_detection,format_yolov5
def receive_image():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建TCP/IP套接字
server_socket.bind(('0.0.0.0', 8000)) # 绑定到本地地址和端口8000
server_socket.listen(1) # 监听连接
print("Waiting for connection...") # 打印等待连接信息
client_socket, addr = server_socket.accept() # 接受连接
print(f"Connected to {addr}") # 打印连接地址
data = b"" # 初始化数据变量
payload_size = struct.calcsize("L") # 获取消息大小的字节数
while len(data) < payload_size: # 接收消息大小
data += client_socket.recv(4096)
packed_msg_size = data[:payload_size] # 获取打包的消息大小
data = data[payload_size:] # 剩余数据
msg_size = struct.unpack("L", packed_msg_size)[0] # 解包消息大小
while len(data) < msg_size: # 接收完整消息
data += client_socket.recv(4096)
frame_data = data[:msg_size] # 获取图像数据
frame = cv2.imdecode(np.frombuffer(frame_data, dtype=np.uint8), cv2.IMREAD_COLOR) # 解码图像
client_socket.close() # 关闭客户端套接字
server_socket.close() # 关闭服务器套接字
return frame # 返回图像
def process_image(image):
height, width = image.shape[:2] # 获取图像高度和宽度
new_height, new_width = height // 2, width // 2 # 计算新高度和宽度
print("Process_image ok !")
return cv2.resize(image, (new_width, new_height)) # 调整图像大小
def send_image(image):
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建TCP/IP套接字
for _ in range(5): # 尝试5次连接
try:
client_socket.connect(('172.17.0.1', 8001)) # 连接到指定地址和端口
break
except ConnectionRefusedError:
print("Connection refused, retrying...")
time.sleep(2) # 等待2秒后重试
else:
print("Failed to connect after 5 attempts")
return
_, img_encoded = cv2.imencode('.jpg', image) # 编码图像为JPEG格式
data = img_encoded.tobytes() # 转换为字节数据
client_socket.sendall(struct.pack("L", len(data)) + data) # 发送数据
client_socket.close() # 关闭客户端套接字
def Yolov5Dnn(image):
print("Yolo detect ")
class_list = load_classes()
colors = [(255, 255, 0), (0, 255, 0), (0, 255, 255), (255, 0, 0)]
is_cuda = 0
net = build_model(is_cuda)
inputImage = format_yolov5(image)
outs = detect(inputImage, net)
class_ids, confidences, boxes = wrap_detection(inputImage, outs[0])
for (classid, confidence, box) in zip(class_ids, confidences, boxes):
color = colors[int(classid) % len(colors)]
cv2.rectangle(image, box, color, 2)
cv2.rectangle(image, (box[0], box[1] - 20), (box[0] + box[2], box[1]), color, -1)
cv2.putText(image, class_list[classid], (box[0], box[1] - 10), cv2.FONT_HERSHEY_SIMPLEX, .5, (0, 0, 0))
return image
if __name__ == "__main__":
while True:
# 接收图像
received_image = receive_image()
# 处理图像
#processed_image = process_image(received_image)
processed_image = Yolov5Dnn(received_image)
# 发送处理后的图像
send_image(processed_image)
4.test1.py文件
这里和前面的代码是一样的
import cv2
import numpy as np
import socket
import struct
def send_image(image_path):
# 读取图像
image = cv2.imread(image_path)
if image is None:
print(f"无法读取图像: {image_path}")
return
# 创建TCP/IP套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 8000)) # 连接到Docker容器的8000端口
# 编码图像为JPEG格式
_, img_encoded = cv2.imencode('.jpg', image)
data = img_encoded.tobytes()
# 发送数据
client_socket.sendall(struct.pack("L", len(data)) + data)
client_socket.close()
print("图像已发送")
def receive_processed_image():
# 创建TCP/IP套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 8001)) # 绑定到本地地址和端口8001
server_socket.listen(1)
print("等待接收处理后的图像...")
client_socket, addr = server_socket.accept()
print(f"已连接到 {addr}")
data = b""
payload_size = struct.calcsize("L")
while len(data) < payload_size:
data += client_socket.recv(4096)
packed_msg_size = data[:payload_size]
data = data[payload_size:]
msg_size = struct.unpack("L", packed_msg_size)[0]
while len(data) < msg_size:
data += client_socket.recv(4096)
frame_data = data[:msg_size]
# 解码图像
frame = cv2.imdecode(np.frombuffer(frame_data, dtype=np.uint8), cv2.IMREAD_COLOR)
client_socket.close()
server_socket.close()
return frame
if __name__ == "__main__":
image_path = "/home/cj/work/Docker/Yolov5DnnImage/bus.jpg" # 替换为实际的图像路径
send_image(image_path)
processed_image = receive_processed_image()
# 显示处理后的图像
#cv2.imshow("Processed Image", processed_image)
#cv2.waitKey(0)
#cv2.destroyAllWindows()
# 保存处理后的图像
cv2.imwrite("processed_image.jpg", processed_image)
print("处理后的图像已保存为 processed_image.jpg")
5.yolo.py文件
这个文件包含的是yolov5调用opencv的dnn模块进行目标检测的一些函数,内容如下:
import cv2
import time
import sys
import numpy as np
def build_model(is_cuda):
net = cv2.dnn.readNet("./yolov5s.onnx")
if is_cuda:
print("Attempty to use CUDA")
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA_FP16)
else:
print("Running on CPU")
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
return net
INPUT_WIDTH = 640
INPUT_HEIGHT = 640
SCORE_THRESHOLD = 0.2
NMS_THRESHOLD = 0.4
CONFIDENCE_THRESHOLD = 0.4
def detect(image, net):
blob = cv2.dnn.blobFromImage(image, 1/255.0, (INPUT_WIDTH, INPUT_HEIGHT), swapRB=True, crop=False)
net.setInput(blob)
preds = net.forward()
return preds
def load_classes():
class_list = []
with open("./classes.txt", "r") as f:
class_list = [cname.strip() for cname in f.readlines()]
return class_list
def wrap_detection(input_image, output_data):
class_ids = []
confidences = []
boxes = []
rows = output_data.shape[0]
image_width, image_height, _ = input_image.shape
x_factor = image_width / INPUT_WIDTH
y_factor = image_height / INPUT_HEIGHT
for r in range(rows):
row = output_data[r]
confidence = row[4]
if confidence >= 0.4:
classes_scores = row[5:]
_, _, _, max_indx = cv2.minMaxLoc(classes_scores)
class_id = max_indx[1]
if (classes_scores[class_id] > .25):
confidences.append(confidence)
class_ids.append(class_id)
x, y, w, h = row[0].item(), row[1].item(), row[2].item(), row[3].item()
left = int((x - 0.5 * w) * x_factor)
top = int((y - 0.5 * h) * y_factor)
width = int(w * x_factor)
height = int(h * y_factor)
box = np.array([left, top, width, height])
boxes.append(box)
indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.25, 0.45)
result_class_ids = []
result_confidences = []
result_boxes = []
for i in indexes:
result_confidences.append(confidences[i])
result_class_ids.append(class_ids[i])
result_boxes.append(boxes[i])
return result_class_ids, result_confidences, result_boxes
def format_yolov5(frame):
row, col, _ = frame.shape
_max = max(col, row)
result = np.zeros((_max, _max, 3), np.uint8)
result[0:row, 0:col] = frame
return result
三、更新并运行docker镜像
1.更新镜像
在文件目录下打开终端,输入:
sudo docker build -t python_socket2 . -f Dockerfile
等待构建完成。
2.运行镜像
终端输入:
udo docker run -p 8000:8000 -i -t python_socket2:latest
宿主机端,运行test1.py文件。
3.查看结果
文件夹下出现processed_image.jpg,可以看到检测结果。