图像畸变-径向切向畸变实时图像RTSP推流
实验环境
注意:ffmpeg进程stdin写入两张图片的时间间隔不能太长,否则mediamtx会出现对应的推流session超时退出。
实验效果
全部代码
my_util.py
#进度条
import os
import sys
import time
import shutil
import logging
import time
from datetime import datetimedef print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=100, fill='█', print_end="\r"):"""调用在Python终端中打印自定义进度条的函数iteration - 当前迭代(Int)total - 总迭代(Int)prefix - 前缀字符串(Str)suffix - 后缀字符串(Str)decimals - 正数的小数位数(Int)length - 进度条的长度(Int)fill - 进度条填充字符(Str)print_end - 行尾字符(Str)"""percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))filled_length = int(length * iteration // total)bar = fill * filled_length + '-' * (length - filled_length)print(f'\r{prefix} |{bar}| {percent}% {suffix}', end=print_end)# 打印新行,完成进度条if iteration == total:print()class Logger(object):"""终端打印不同颜色的日志"""ch = logging.StreamHandler() # 创建日志处理器对象,在__init__外创建,是类当中的静态属性,不是__init__中的实例属性# #创建静态的日志处理器可以减少内存消耗# # 创建 FileHandler 实例,指定日志文件路径# ch = logging.FileHandler(filename='app1.log')def __init__(self):self.logger = logging.getLogger() # 创建日志记录对象self.logger.setLevel(logging.DEBUG) # 设置日志等级info,其他低于此等级的不打印def debug(self, message):self.fontColor('\033[0;37m%s\033[0m')self.logger.debug(message)def info(self, message):self.fontColor('\033[0;32m%s\033[0m')self.logger.info(message)def warning(self, message):self.fontColor('\033[0;33m%s\033[0m')self.logger.warning(message)def error(self, message):self.fontColor('\033[0;31m%s\033[0m')self.logger.error(message)def fontColor(self, color):formatter = logging.Formatter(color % '%(asctime)s - %(name)s - %(levelname)s - %(message)s') # 控制日志输出颜色self.ch.setFormatter(formatter)self.logger.addHandler(self.ch) # 向日志记录对象中加入日志处理器对象def delete_files(folder_path, max_files):"""监控指定文件夹中的文件数量,并在超过max_files时删除最旧的文件。"""print("进入删除图片文件夹"+folder_path)print("需要删除文件数量")print(max_files)if True:# 获取文件夹中的文件列表files = os.listdir(folder_path)file_count = len(files)print(f"当前文件夹 {folder_path} 中的文件数量: {file_count}")# 如果文件数量超过max_files,则删除最旧的文件if file_count > max_files:# 获取文件夹中所有文件的完整路径,并带上修改时间file_paths_with_mtime = [(os.path.join(folder_path, f), os.path.getmtime(os.path.join(folder_path, f))) forf in files]# 按修改时间排序sorted_files = sorted(file_paths_with_mtime, key=lambda x: x[1])# 删除最旧的文件,直到文件数量在阈值以下for file_path, mtime in sorted_files[:file_count - max_files]:try:os.remove(file_path)print(f"已删除文件: {file_path}")except OSError as e:print(f"删除文件时出错: {e.strerror}")def copy_file(src, dst):shutil.copy2(src, dst) # copy2会尝试保留文件的元数据def end_sentence(text, max_length):'''保证在max_length长度前以句号或点号结束文本:param text: 文本:param max_length: 最大长度:return:'''# 如果文本长度已经超过最大长度,则直接截断if len(text) > max_length:text = text[:max_length]# print("结果长度 {}".format(len(text)))# 查找句号的位置(en)period_index = max(text.rfind('.'), text.rfind(','),text.rfind(':'), text.rfind(';'),text.rfind('!'), text.rfind('?')) # 从后往前找,找到最后一个句号# 如果找到了句号且它在最大长度内if period_index != -1 and (period_index + 1 < max_length ormax_length == -1):# 如果需要替换,则替换句号text = text[:period_index] + '.'# 查找句号的位置(cn)period_index = max(text.rfind('。'), text.rfind(','),text.rfind(':'), text.rfind(';'),text.rfind('!'), text.rfind('?')) # 从后往前找,找到最后一个句号# 如果找到了句号且它在最大长度内if period_index != -1 and (period_index + 1 < max_length ormax_length == -1):# 如果需要替换,则替换句号text = text[:period_index] + '。'return textimport base64def encode_base64(input_string):"""对字符串进行Base64编码"""encoded_bytes = base64.b64encode(input_string.encode('utf-8'))encoded_string = encoded_bytes.decode('utf-8')return encoded_stringdef decode_base64(input_string):"""对Base64编码的字符串进行解码"""decoded_bytes = base64.b64decode(input_string.encode('utf-8'))decoded_string = decoded_bytes.decode('utf-8')return decoded_stringimport socketdef get_local_ip():try:# 创建一个 UDP 套接字s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 连接到一个公共的 IP 地址和端口s.connect(("8.8.8.8", 80))# 获取本地 IP 地址local_ip = s.getsockname()[0]s.close()return local_ipexcept Exception as e:print(f"获取本地 IP 地址时出错: {e}")return None
make_pics.py
import numpy as np
import cv2
import math
import time
from PIL import Image, ImageDraw, ImageFontdef distort_image(image, k1, k2, p1, p2):"""对图像应用径向和切向畸变:param image: 输入图像:param k1: 径向畸变系数:param k2: 径向畸变系数:param p1: 切向畸变系数:param p2: 切向畸变系数:return: 畸变后的图像"""h, w = image.shape[:2]camera_matrix = np.array([[w, 0, w / 2],[0, h, h / 2],[0, 0, 1]], dtype=np.float32)distort_coeffs = np.array([k1, k2, p1, p2, 0], dtype=np.float32)# 生成畸变映射map1, map2 = cv2.initUndistortRectifyMap(camera_matrix, distort_coeffs, np.eye(3), camera_matrix, (w, h), cv2.CV_32FC1)# 应用畸变映射distorted_img = cv2.remap(image, map1, map2, cv2.INTER_LINEAR)return distorted_imgdef put_chinese_text(img, text, position, font_path, font_size, color):"""在图像上添加中文文字:param img: 输入图像:param text: 要添加的文字:param position: 文字位置:param font_path: 字体文件路径:param font_size: 字体大小:param color: 文字颜色:return: 添加文字后的图像"""img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))draw = ImageDraw.Draw(img_pil)font = ImageFont.truetype(font_path, font_size)draw.text(position, text, font=font, fill=color)return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)def make_pics(pic_path="picture.jpg", k1=-0.5, k2=0.0, p1=0.0, p2=0.0):# 生成棋盘图像chessboard = np.zeros((400, 400, 3), dtype=np.uint8)for i in range(0, 400, 40):for j in range(0, 400, 40):if (i // 40 + j // 40) % 2 == 0:chessboard[i:i + 40, j:j + 40] = [255, 255, 255]# 生成雷达图radar = chessboard.copy()x0, y0 = radar.shape[1] // 2, radar.shape[0] // 2for radius in range(0, 400, 40):cv2.circle(radar, (x0, y0), radius, (0, 0, 255), 1)# 绘制径向线for angle in range(0, 360, 40):# 使用最大半径 400 计算径向线的终点坐标x = int(x0 + 400 * math.cos(math.radians(angle)))y = int(y0 + 400 * math.sin(math.radians(angle)))cv2.line(radar, (x0, y0), (x, y), (0, 0, 255), 1)font_size = 15font_color = (250, 100, 0)combined_distorted_chessboard = distort_image(radar, k1, k2, p1, p2)text1 = "k1={:.2f},k2={:.2f},p1={:.2f},p2={:.2f}".format(k1,k2,p1,p2)text2 = "图像畸变"combined_distorted_chessboard = put_chinese_text(combined_distorted_chessboard, text1, (10, 30), 'simhei.ttf', font_size, font_color)combined_distorted_chessboard = put_chinese_text(combined_distorted_chessboard, text2, (10, 60), 'simhei.ttf', font_size, font_color)# 保存图像cv2.imwrite(pic_path, combined_distorted_chessboard)# cv2.imshow(pic_path, combined_distorted_chessboard)# cv2.waitKey(0)# cv2.destroyAllWindows()returnif False:for k1 in np.arange(-100,100,0.1):for k2 in np.arange(-100, 100, 0.1):for p1 in np.arange(-100, 100, 0.1):for p2 in np.arange(-100, 100, 0.1):make_pics("picture.jpg", k1, k2, p1, p2)
pic_2_rtsp.py
import numpy as np
import make_pics
import sys
import msvcrt
import subprocess
import time
import shlex
import my_util
from PIL import Image, ImageDraw
import random
import oslog = my_util.Logger()
# RTSP_DEF_IP = "192.168.31.185"
RTSP_DEF_IP = my_util.get_local_ip()
RTSP_PORT = 8554
local_ip = my_util.get_local_ip()
if local_ip:RTSP_URL = "rtsp://{}:{}/live".format(local_ip, RTSP_PORT)
else:RTSP_URL = "rtsp://{}:{}/live".format(RTSP_DEF_IP, RTSP_PORT)
frame_duration = 1/25 # 每张图片显示的时长(秒),process.stdin.wirte写入速度需要够快,否则可能接收端接受数据不足无法获取解码信息(抓包看看)
frame_num = 0old_picname = "past.jpg"
new_picname = "now.jpg"
k1 = k2 = p1 = p2 = 0.0def generate_orig(old_picname):"""生成默认图片"""image = Image.new('RGB', (640, 480), color=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))draw = ImageDraw.Draw(image)draw.text((100, 100), 'No Signal', fill=(255, 255, 255))image.save(old_picname)returndef generate_image(new_picname, k1, k2, p1, p2):"""生成图片"""make_pics.make_pics(new_picname, k1, k2, p1, p2)return# 构建 ffmpeg 命令使用图片序列
command_line = 'ffmpeg -loglevel error -re -i - -c:v libx264 -pix_fmt yuv420p -r {} -f rtsp {}'.format(1/frame_duration, RTSP_URL)
command_list = shlex.split(command_line)log.debug(command_list)def start_process():global processprocess = subprocess.Popen(command_list, stdin=subprocess.PIPE, text=False)log.info("Process started.")start_time = time.time()class QuitException(Exception):passtry:# 默认先生成初始图step_length = 0.125generate_orig(old_picname)start_process()while True:for p1 in np.arange(-1 * step_length, step_length, step_length):for p2 in np.arange(-1 * step_length, step_length, step_length):for k1 in np.arange(-1, 1, step_length):for k2 in np.arange(-1, 1, step_length):log.debug("畸变系数 k1={}, k2={}, p1={}, p2={}".format(k1, k2, p1, p2))generate_image(new_picname, k1, k2, p1, p2)if msvcrt.kbhit(): # 检查是否有键盘输入input_char = msvcrt.getch().decode('utf-8')if input_char == 'q' or input_char == 'Q':try:# 向进程的标准输入发送 'q' 并换行if process.stdin:process.stdin.write('q\n'.encode())process.stdin.flush()except Exception as e:passraise QuitException()# 持续生成新图片替换旧图片try:if os.path.exists(new_picname):with open(new_picname, 'rb') as f:process.stdin.write(f.read())else:with open(old_picname, 'rb') as f:process.stdin.write(f.read())except Exception as e:log.error(f"Error writing to process stdin: {e}")log.info("Restarting process...")process.terminate()try:process.wait(timeout=1)except subprocess.TimeoutExpired:process.kill()start_process()time.sleep(frame_duration)except QuitException:pass
finally:try:process.terminate()try:process.wait(timeout=1)except subprocess.TimeoutExpired:process.kill()except Exception:passtry:if os.path.exists(new_picname):os.remove(new_picname)except Exception as e:log.error(f"Error removing {new_picname}: {e}")end_time = time.time()time_cnt = end_time - start_timelog.info("FFmpeg进程已执行{}秒并通过输入 'q' 退出。".format(round(time_cnt)))
相关文章:

图像畸变-径向切向畸变实时图像RTSP推流
实验环境 注意:ffmpeg进程stdin写入两张图片的时间间隔不能太长,否则mediamtx会出现对应的推流session超时退出。 实验效果 全部代码 my_util.py #进度条 import os import sys import time import shutil import logging import time from datetime i…...

手搓雷达图(MATLAB)
看下别人做出来什么效果 话不多说,咱们直接开始 %% 可修改 labels {用户等级, 发帖数, 发帖频率, 点度中心度, 中介中心度, 帖子类型计分, 被列为提案数}; cluster_centers [0.8, 4.5, 3.2, 4.0, 3.8, 4.5, 4.2; % 核心用户0.2, 0.5, 0.3, 0.2, 0.1, 0.0, 0.0;…...

汽车零配件供应商如何通过EDI与主机厂生产采购流程结合
当前,全球汽车产业正经历深刻的数字化转型,供应链协同模式迎来全新变革。作为产业链核心环节,汽车零部件供应商与主机厂的高效对接已成为企业发展的战略要务。然而,面对主机厂日益严格的数字化采购要求,许多供应商在ED…...

闻性与空性:从耳根圆通到究竟解脱的禅修路径
一、闻性之不动:超越动静的觉性本质 在《楞严经》中,佛陀以钟声为喻揭示闻性的奥秘:钟声起时,闻性显现;钟声歇时,闻性不灭。此“不动”并非如磐石般凝固,而是指觉性本身超越生灭、来去的绝对性…...

第34课 常用快捷操作——按“空格键”旋转图元
概述 旋转某个图元,是设计过程中常需要用到的操作,无论是在原理图中旋转某个图形,还是在PCB图中旋转某个元素。 旋转操作的快捷键是空格键。下面作详细介绍。 按空格键旋转图元 当我们选中一个图元时,按下空格键,即…...

基于亚马逊云科技构建音频转文本无服务器应用程序
Amazon Transcribe是一项基于机器学习模型自动将语音转换为文本的服务。它提供了多种可以提高文本转录准确性的功能,例如语言自定义、内容过滤、多通道音频分析和说话人语音分割。Amazon Transcribe 可用作独立的转录服务,也可以集成到应用程序中提供语音…...
如何打包python程序为可执行文件
将 Python 程序打包为可执行文件是一个常见需求,尤其是在希望将应用程序分享给不具备 Python 环境的用户时。以下是使用 PyInstaller 工具将 Python 程序打包为可执行文件的步骤。 步骤 1:安装 PyInstaller 如果您还没有安装 PyInstaller,请…...

计算机二级MS Office第八套演示文稿
教程:...

K8S Service 原理、案例
一、理论介绍 1.1、3W 法则 1、是什么? Service 是一种为一组功能相同的 pod 提供单一不变的接入点的资源。当 Service 存在时,它的IP地址和端口不会改变。客户端通过IP地址和端口号与 Service 建立连接,这些连接会被路由到提供该 Service 的…...

实验四 进程调度实验
一、实验目的 1、了解操作系统CPU管理的主要内容。 2、加深理解操作系统管理控制进程的数据结构--PCB。 3、掌握几种常见的CPU调度算法(FCFS、SJF、HRRF、RR)的基本思想和实现过程。 4、用C语言模拟实现CPU调度算法。 5、掌握CPU调度算法性能评价指…...

ABAP Object Services
ABAP Object Services...

linux blueZ 第四篇:BLE GATT 编程与自动化——Python 与 C/C++ 实战
本篇聚焦 BLE(Bluetooth Low Energy)GATT 协议层的编程与自动化实践,涵盖 GATT 基础、DBus API 原理、Python(dbus-next/bleak)示例、C/C++ (BlueZ GATT API)示例,以及自动发现、读写特征、订阅通知、安全配对与脚本化测试。 目录 BLE GATT 基础概念 BlueZ DBus GATT 模…...

Linux线程与进程:探秘共享地址空间的并发实现与内
Linux系列 文章目录 Linux系列前言一、线程的概念二、线程与地址空间2.1 线程资源的分配2.2 虚拟地址到物理地址的转换 三 、线程VS进程总结 前言 在Linux操作系统中,线程作为CPU调度的基本单位,起着至关重要的作用。深入理解线程控制机制,是…...
ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0
在遇到这个代码时,大多数情况下就是两个运算的向量维度不匹配,此时,可以打印一下两个数组的维度, # print(“[DEBUG] a shape:”, a.shape) # print(“[DEBUG]b:”, b.shape) 假设a.shape结果为[,200],b.shape结果为[210,255],那么…...
MySQL 8.4企业版 安装和配置审计插件
在最新的MySQL 8.4.4企业版上启用审计日志功能 操作系统:Ubuntu 24.04 数据库:8.4.4-commercial for Linux on x86_64 (MySQL Enterprise Server - Commercial) 1.查看安装脚本 下面2个脚本位于mysql安装目录 share 下,一个是window一个是linux可以用…...

科学养生,开启健康生活新方式
在快节奏的现代生活中,健康养生已成为人们关注的焦点。科学的养生方式不仅能增强体质,还能有效预防疾病,提升生活质量。 合理饮食是健康养生的基础。日常饮食应遵循均衡原则,保证蛋白质、碳水化合物、脂肪、维生素和矿物质的合…...

外贸图片翻译软件推荐用哪些?不损原图画质的跨境图片翻译器,收藏!
在跨境电商的 “江湖” 里,卖家们怀揣着全球 “捞金” 的梦想扬帆起航,可谁能想到,一个看似不起眼的 “小怪兽”—— 图片翻译难题,却常常让大家在 “出海” 途中 “栽跟头”。 电商跨境图片翻译全能王——风车AI翻译 [fengchef…...

3.1/Q1,Charls最新文章解读
文章题目:The impact of chronic diseases and lifestyle on sarcopenia risk in older adults: a population-based longitudinal study DOI:10.3389/fmed.2025.1500915 中文标题:慢性病和生活方式对老年人肌肉减少症风险的影响:…...

简单几步,开启 Intel VT-x 让电脑“解开CPU封印”
#vmware #虚拟机 #cpu虚拟化 # Intel VT-x 前言 你是不是也遇到过这种情况:在尝试运行虚拟机(VM)、安卓模拟器,或者使用 Windows 沙盒、WSL2 等功能时,遇到了类似“此主机支持 Intel VT-x,但 Intel VT-x …...

flutter 插件收集
2025年 1月10号Flutter插件手机 声音转文字 speech_to_text | Flutter package 文字转声音 flutter_tts | Flutter package 堆栈信息 stack_trace | Dart package 跳转到app设置里面 app_settings | Flutter package 轻松的动画 animations | Flutter package 日志打印 t…...
Golang编程拒绝类型不安全
简介 在 Go 中,标准库提供了多种容器类型,如 list、ring、heap、sync.Pool 和 sync.Map。然而,这些容器默认是类型不安全的,即它们可以接受任何类型的值,这可能导致运行时错误。为了提升代码的类型安全性和可维护性&am…...

pyenv-virtualenv(python 版本管理工具)
推荐参考(本人实测有用) 参考文章pyenv 和 pyenv-virtualenv 的安装、配置和使用(仅供参考) 参考文章 pyenvpyenv-virtualenv(仅供参考) pyenv (windows)安装 手动安装 git clone https://github.com/pye…...

DocsGPT remote接口RCE(CVE-2025-0868)
免责声明 本文档所述漏洞详情及复现方法仅限用于合法授权的安全研究和学术教育用途。任何个人或组织不得利用本文内容从事未经许可的渗透测试、网络攻击或其他违法行为。使用者应确保其行为符合相关法律法规,并取得目标系统的明确授权。 对于因不当使用本文信息而造成的任何直…...
VuePress可以做什么?
VuePress 可以做什么 VuePress 是一个基于 Vue.js 的静态站点生成器,专注于文档和内容展示。它结合了 Markdown 的简洁性和 Vue 的灵活性,适合多种场景的开发需求。以下是 VuePress 的主要用途和功能: 1. 技术文档网站 VuePress 最初是为编写 Vue.js 官方文档而设计的,因…...

消息中间件RabbitMQ-01:简要介绍及其Windows安装流程
一、简要介绍 定义:RabbitMQ 是一个开源消息中间件,用于实现消息队列和异步通信。 场景:适用于分布式系统、异步任务处理、消息解耦、高并发访问等场景。 比喻:RabbitMQ 就像是邮政公司,负责在不同系统间安全快速地传…...

(二)读写分离架构、冷热分离架构
文章目录 读写分离架构什么是读写分离结构架构模型优缺点优点缺点 技术案例写情况读情况 冷热分离架构什么是冷热分离架构?架构模型优缺点优点 缺点技术案例读数据写数据 读写分离架构 什么是读写分离结构 读写分离架构针对于数据库。数据库原本负责读写两个功能。 读写分离架…...

TS-300B浊度传感器详解(STM32)
目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 三、程序设计 main文件 ts.h文件 ts.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 TS-300B浊度传感器介绍: 浊度是指溶液对光线通过时所产生的阻碍程度,它包括悬浮物对光的散射和…...
【特殊场景应对8】LinkedIn式动态简历的利弊分析:在变革与风险间走钢丝
写在最前 作为一个中古程序猿,我有很多自己想做的事情,比如埋头苦干手搓一个低代码数据库设计平台(目前只针对写java的朋友),比如很喜欢帮身边的朋友看看简历,讲讲面试技巧,毕竟工作这么多年,也做到过高管,有很多面人经历,意见还算有用,大家基本都能拿到想要的offe…...

Unity Paint In 3D 入门
插件版本4.1.8 快速开始 这是一个强大的,可自由涂鸦的Unity插件. 步骤1 任何带有 MeshFilter MeshRenderer 或 SkinnedMeshRenderer 的 GameObject 均可被喷涂。 方法1 为 GameObject 添加 CwPaintableMesh 组件。 方法2 点击 MeshRenderer 或 Skinne…...
风车邮箱系统详细使用指南:Windows与Ubuntu双平台解析
风车邮箱系统V1.2使用手册 风车邮箱系统详细使用指南:Windows与Ubuntu双平台解析 前言 在日常网络活动中,我们经常需要一个临时邮箱来注册各类网站或接收验证码,但不想使用自己的真实邮箱。「风车无线邮箱系统」作为一款优秀的临时邮箱工具…...