pythonproject/demo.py
2024-06-25 14:15:07 +08:00

491 lines
21 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
from datetime import datetime
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, DateTime,TIMESTAMP
import psycopg2
import cv2
import numpy as np
from numpy.fft import fft2, fftshift, ifft2, ifftshift
import matplotlib.pyplot as plt
from PIL import Image, ImageOps
import random
from skimage.measure import label, regionprops
import math
import json
# 创建连接引擎
engine = create_engine('postgresql://postgres:!1q2w3E*@8.134.85.73:5432/postgres')
# 连接数据库
connection = engine.connect()
print("Connection to PostgreSQL DB successful")
# 声明表结构
metadata = MetaData()
qrcode_data = Table('qrcode_data', metadata,
Column('key', String),
Column('circles', String),
# createdate 时间类型
Column('createdate', TIMESTAMP)
)
def create_filter_with_only_circles_and_generate(size, cutoff_radius, num_circles, circle_radius):
"""
创建一个仅包含指定数量圆形的滤波器。
参数:
size: 滤波器的大小(宽度和高度相同)。
cutoff_radius: 低频区域的半径,确保所有圆都在此区域内。
num_circles: 滤波器中要生成的圆的数量。
circle_radius: 圆的半径。
返回值:
一个二进制滤波器数组其中圆的区域值为1其他区域值为0。
"""
filter = np.zeros((size, size)) # 初始化滤波器数组
center_x, center_y = size // 2, size // 2 # 计算滤波器中心点坐标
circles = [] # 存储已生成圆的列表
max_attempts = 1000 # 设置最大尝试次数,以避免无限循环
while len(circles) < num_circles and max_attempts > 0: # 循环,直到生成足够数量的不重叠圆
# 随机生成圆心位置,确保圆完全位于低频区域内
angle = random.uniform(0, 2 * np.pi)
r = random.uniform(0, cutoff_radius - circle_radius)
x = int(center_x + r * np.cos(angle))
y = int(center_y + r * np.sin(angle))
# 检查新生成的圆是否与已存在的圆重叠
overlapping = False
for cx, cy, cr in circles:
if np.sqrt((x - cx) ** 2 + (y - cy) ** 2) < (circle_radius + cr):
overlapping = True
break
if not overlapping: # 如果新圆不与任何已存在圆重叠,则添加到列表中
circles.append((x, y, circle_radius))
max_attempts -= 1
# 为每个生成的圆在滤波器数组中设置对应区域的值为1
for (x, y, radius) in circles:
for i in range(-radius, radius + 1):
for j in range(-radius, radius + 1):
if np.sqrt(i ** 2 + j ** 2) <= radius:
filter[x + i, y + j] = 1 # 设置圆形区域的值为1
return filter
def generate_circles_in_frequency_domain(size, cutoff_radius, num_circles, circle_radius):
"""
生成指定数量的圆形,用于频域操作。
参数:
size: 频域的大小(宽度和高度相同)。
cutoff_radius: 低频区域的半径,确保所有圆都在此区域内。
num_circles: 要生成的圆的数量。
circle_radius: 圆的半径。
返回值:
一个列表,包含每个圆的 (中心x坐标, 中心y坐标, 半径) 元组。
"""
center_x, center_y = size // 2, size // 2 # 计算频域中心点坐标
circles = [] # 存储已生成圆的列表
max_attempts = 1000 # 设置最大尝试次数,以避免无限循环
while len(circles) < num_circles and max_attempts > 0: # 循环,直到生成足够数量的不重叠圆
# 随机生成圆心位置,确保圆完全位于低频区域内
angle = random.uniform(0, 2 * np.pi)
r = random.uniform(0, cutoff_radius - circle_radius)
x = int(center_x + r * np.cos(angle))
y = int(center_y + r * np.sin(angle))
# 检查新生成的圆是否与已存在的圆重叠
overlapping = False
for cx, cy, cr in circles:
if np.sqrt((x - cx) ** 2 + (y - cy) ** 2) < (circle_radius + cr):
overlapping = True
break
if not overlapping: # 如果新圆不与任何已存在圆重叠,则添加到列表中
circles.append((x, y, circle_radius))
max_attempts -= 1
return circles
def draw_circles_on_frequency_domain(size, circles):
"""
在频域图中绘制圆形。
参数:
size: 频域的大小(宽度和高度相同)。
circles: 包含每个圆的 (中心x坐标, 中心y坐标, 半径) 元组的列表。
返回值:
一个二维数组表示频域图其中圆形区域的值为1其他区域的值为0。
"""
frequency_domain_image = np.zeros((size, size)) # 初始化频域图数组
# 遍历所有圆并在频域图中绘制它们
for (x, y, radius) in circles:
for i in range(-radius, radius + 1):
for j in range(-radius, radius + 1):
if i**2 + j**2 <= radius**2: # 判断点是否在圆内
# 检查绘制点是否在图像范围内
if 0 <= x + i < size and 0 <= y + j < size:
frequency_domain_image[x + i, y + j] = 1
return frequency_domain_image
def find_circles_in_filter(filter_array):
"""
从滤波器数组中识别圆形区域,提取每个圆的中心和半径。
参数:
filter_array (ndarray): 一个二维数组其中圆形区域的值为1其他为0。
返回:
list: 每个圆的 (中心x坐标, 中心y坐标, 半径) 元组列表。
"""
labeled_image = label(filter_array) # 标记连通区域
regions = regionprops(labeled_image) # 提取区域属性
circles = []
for region in regions:
if region.area >= 5: # 排除太小的区域,假定它们不是我们要找的圆
radius = np.sqrt(region.area / np.pi) # 计算半径
circles.append((region.centroid[1], region.centroid[0], radius)) # x, y顺序要反转因为centroid给出的是(row, col)
return circles
def distribute_points_on_circles(f_shifted, circles, num_points=240):
"""
将点均匀分布在每个圆周上。
参数:
f_shifted: 平移处理的函数图像
circles (list of tuples): 每个元组包含一个圆的 (中心x坐标, 中心y坐标, 半径)
num_points (int): 每个圆上的点数量默认为240
返回:
list: 每个圆的点坐标列表每个元素为一个包含240个点的列表每个点是(x, y)坐标的元组
"""
radians_per_step = 2 * np.pi / num_points # 计算每个步长的弧度值
print('弧度:', radians_per_step)
all_circle_points = [] # 存储所有圆的点坐标
for center_x, center_y, radius in circles:
# print('中心坐标:', center_x, center_y)
circle_points = [] # 存储单个圆的点坐标
for i in range(num_points):
theta = i * radians_per_step # 计算当前点的角度弧度
x = center_x + radius * np.cos(theta) # 极坐标转笛卡尔坐标x
y = center_y + radius * np.sin(theta) # 极坐标转笛卡尔坐标y
#通过笛卡尔坐标计算回角度
angle_radians = math.atan2(y - center_y, x - center_x)
angle_degrees = math.degrees(angle_radians)
#保留angle_degrees小数点后1位四舍五入
angle_degrees = round(angle_degrees, 1)
#计算x,y坐标点的幅度
amplitude = np.abs(f_shifted[int(x), int(y)])
#计算x,y坐标点的相位
phase = np.angle(f_shifted[int(x), int(y)])
#将当前点的坐标,幅度以及相位打印出来
# print(f"x: {x}, y: {y}, angle: {angle_degrees}, amplitude: {amplitude}, phase: {phase}")
# 将当前点的坐标,幅度以及相位添加到列表
circle_points.append((int(x), int(y), angle_degrees, amplitude, phase)) # 添加当前点的坐标到列表
all_circle_points.append(circle_points) # 添加该圆的所有点坐标到主列表
#打印出circle_points中最大的幅度
# print(f"最大幅度:{max(circle_points, key=lambda x: x[3])[3]}")
# 将all_circle_points每个元素的点坐标根据amplitude进行降序排序
# for i in range(len(all_circle_points)):
# all_circle_points[i].sort(key=lambda x: x[3], reverse=True)
packed_circle_points = []
for circle_points in all_circle_points:
x, y, angles, amplitudes, phase = zip(*circle_points)
packed_circle_points.append((x, y, angles, amplitudes, phase))
return packed_circle_points
def degrees_to_radians(degrees):
"""
将角度转换为弧度。
参数:
degrees (float): 输入的角度值。
返回:
float: 转换后的弧度值。
"""
# 将角度乘以π/180完成角度到弧度的转换
radians = degrees * (math.pi / 180)
return radians
# 将水印序列编码为二进制字符串
def encode_watermark_to_binary(watermark_sequence):
binary_sequence = []
for number in watermark_sequence:
# 初始化一个100位全为'0'的二进制字符串
binary_100_bit = ['0'] * 100
# 在数字对应的位置设置'1'
if 0 <= number < 100:
binary_100_bit[number-1] = '1'
# 连接这些位形成一个100位的二进制字符串
binary_sequence.append(''.join(binary_100_bit))
return binary_sequence
#解析图片水印
def process_image_b_component(image_path, circles, watermarks):
num_points = 240
img = Image.open(image_path)
img = np.array(img)
# 将img缩放到 256*256
# img = cv2.resize(img, (339,339))
# img = cv2.resize(img, (500, 500))
img = cv2.resize(img, (255, 255))
b_channel = img[:, :, 2]
# 计算二维DFT并移位使零频率分量在中心
f_transform = fft2(b_channel)
f_shifted = fftshift(f_transform)
radians_per_step = 2 * np.pi / num_points # 计算每个步长的弧度值
all_circle_points = [] # 存储所有圆的点坐标
for center_x, center_y, radius in circles:
# print('中心坐标:', center_x, center_y)
circle_points = [] # 存储单个圆的点坐标
for i in range(num_points):
theta = i * radians_per_step # 计算当前点的角度弧度
x = center_x + radius * np.cos(theta) # 极坐标转笛卡尔坐标x
y = center_y + radius * np.sin(theta) # 极坐标转笛卡尔坐标y
# 通过笛卡尔坐标计算回角度
angle_radians = math.atan2(y - center_y, x - center_x)
angle_degrees = math.degrees(angle_radians)
# 保留angle_degrees小数点后1位四舍五入
angle_degrees = round(angle_degrees, 1)
# 计算x,y坐标点的幅度
amplitude = np.abs(f_shifted[int(x), int(y)])
# 计算x,y坐标点的相位
phase = np.angle(f_shifted[int(x), int(y)])
# 将当前点的坐标,幅度以及相位打印出来
print(f"x: {int(x)}, y: {int(y)}, angle: {angle_degrees}, amplitude: {amplitude}, phase: {phase}")
# 将当前点的坐标,幅度以及相位添加到列表
circle_points.append((int(x), int(y), angle_degrees, amplitude, phase)) # 添加当前点的坐标到列表
all_circle_points.append(circle_points) # 添加该圆的所有点坐标到主列表
watermark_result = []
#遍历all_circle_points
for i in range(len(all_circle_points)):
print(f"&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 第{i + 1}个圆")
w = watermarks[i]
isOk = False
# 将all_circle_points每个元素的点坐标根据amplitude进行降序排序
all_circle_points[i].sort(key=lambda x: x[3], reverse=True)
# 打印all_circle_points[i]中每个元素
for j in range(16): # 只考虑每个圆的前四个最大幅度
point = all_circle_points[i][j]
print(f"幅度排名 {j + 1}: x: {point[0]}, y: {point[1]}, 角度: {point[2]}, 幅度: {point[3]}, 相位: {point[4]}")
# 处理角度使其在0到360度范围内
adjusted_angle = point[2] if point[2] >= 0 else point[2] + 360
# 计算并打印当前角度是圆上的第几份
fraction_index = adjusted_angle / 1.5 + 1
if w == fraction_index:
print(f"水印匹配成功!角度:{adjusted_angle} 编码:{fraction_index}")
# 将当前fraction_index添加到结果列表中保留整数位
watermark_result.append(int(fraction_index))
isOk = True
break
if isOk == False:
watermark_result.append(0)
return watermark_result, img
def calculate_psnr(image1, image2):
mse = np.mean((image1 - image2) ** 2)
if mse == 0:
return float('inf')
max_pixel = 255.0
psnr = 20 * np.log10(max_pixel / np.sqrt(mse))
return psnr
def perspective_transform(img_path):
if not os.path.exists(img_path):
raise FileNotFoundError(f"Image file '{img_path}' not found.")
# 通过cv2加载图片
img = cv2.imread(img_path)
if img is None:
raise FileNotFoundError(f"Image file '{img_path}' not found or could not be read.")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 复制图片
img_copy = img.copy()
# 将图片转灰度图
img_copy = cv2.cvtColor(img_copy, cv2.COLOR_BGR2GRAY)
# 黑白颜色反转
img_copy = cv2.bitwise_not(img_copy)
wechatqr = cv2.wechat_qrcode.WeChatQRCode("wechat_qrcode/detect.prototxt", "wechat_qrcode/detect.caffemodel",
"wechat_qrcode/sr.prototxt", "wechat_qrcode/sr.caffemodel")
res, points = wechatqr.detectAndDecode(img_copy)
# 在原图上标记二维码的角点
if len(res) > 0:
# for point in points[0]:
# cv2.circle(img, tuple(int(i) for i in point), 10, (255, 0, 0), -1) # 红色圆点半径为10
point = points[0]
width = max(np.linalg.norm(point[0] - point[1]), np.linalg.norm(point[2] - point[3]))
height = max(np.linalg.norm(point[1] - point[2]), np.linalg.norm(point[3] - point[0]))
# 不添加边距
points_dst = np.array([[0, 0], [width, 0], [width, height], [0, height]], dtype=point.dtype)
# 生成变换矩阵
matrix = cv2.getPerspectiveTransform(point, points_dst)
# 应用透视变换,纠正图像
corrected_image = cv2.warpPerspective(img, matrix, (int(width), int(height)))
return corrected_image, img, res[0] # 返回校正后的图像和标记后的原图
return None, img, None # 如果没有检测到二维码,仅返回原图
def exec_image_result(img_path):
# 设置图像大小和截止频率半径
cutoff_radius = 80
# 在低通滤波器中添加8个随机圆形区域
circle_radius = 15 # 每个圆的半径
num_circles = 8 # 圆形数量
alpha = 1.5
# 加载图片
# img_path = 'qr8_1000.png'
# img = Image.open(img_path)
# img = np.array(img)
corrected_image, img, qr_res = perspective_transform(img_path)
# 复制一份原图
img_copy = corrected_image.copy()
# 获取img_copy的size
N, M, _ = img_copy.shape
print(f"img_copy size: {json.dumps(img_copy.shape)}")
# 提取B通道
b_channel = corrected_image[:, :, 2]
N, M = b_channel.shape
# 计算二维DFT并移位使零频率分量在中心
f_transform = fft2(b_channel)
f_shifted = fftshift(f_transform)
# watermark_sequence = [77, 23, 51, 48, 44, 55, 22, 66] # 示例数字
watermark_sequence = [int(qr_res[i:i + 2]) for i in range(0, len(qr_res), 2)]
encoded_watermark = encode_watermark_to_binary(watermark_sequence)
# 展开所有的二进制编码成一个长字符串
flattened_encoded_watermark = ''.join(encoded_watermark)
print(flattened_encoded_watermark)
# 生成了指定数量的位于频域中的圆
circles = generate_circles_in_frequency_domain(b_channel.shape[0], cutoff_radius, num_circles, circle_radius)
# 绘制圆形
frequency_domain_image = draw_circles_on_frequency_domain(b_channel.shape[0], circles)
# 将circles转成 json
circles_json = json.dumps(circles)
print(circles_json)
# 获取当前时间
print(datetime.now())
# 保存数据
new_qr_data = qrcode_data.insert().values(key=json.dumps(watermark_sequence), circles=json.dumps(circles),
createdate=datetime.now())
connection.execute(new_qr_data)
connection.commit()
# connection.close()
distributed_points = distribute_points_on_circles(f_shifted, circles, 240)
# 总幅度
f_amplitudes = np.abs(f_shifted)
# 总相位
f_phase = np.angle(f_shifted)
for circle_index, (x, y, angles, amplitudes, phase) in enumerate(distributed_points):
# 根据指定的圆圈索引,从编码后的水印中获取对应的元素
cw = encoded_watermark[circle_index]
# 获取amplitudes的最大值
max_amplitude = max(amplitudes)
print(f"{circle_index + 1} 的数据: 编码:{cw} ,最大幅度:{max_amplitude}")
# 这里我们可以访问每个圆的所有点的坐标、角度和幅度
for i in range(len(x)): # 假设每个圆有相同数量的点例如240个
# print(f"点 {i + 1}: x = {int(x[i])}, y = {int(y[i])}, 角度 = {angles[i]}, 幅度 = {amplitudes[i]}, 相位 = {phase[i]}")
if i < len(cw): # 确保不会超出水印信息的长度
bit = cw[i] # 根据索引获取水印信息的当前位
if bit == '1':
original_amplitude = f_amplitudes[x[i], y[i]]
f_amplitudes[x[i], y[i]] = max_amplitude * alpha
f_amplitudes[N - x[i], M - y[i]] = max_amplitude * alpha
modified_amplitude = f_amplitudes[x[i], y[i]] # alpha是定义的强度因子
# print(f"点 {i + 1}: x = {int(x[i])}, y = {int(y[i])}, 角度 = {angles[i]}, 幅度 = {amplitudes[i]}, 相位 = {phase[i]}")
# print(f"原始幅度:{original_amplitude}")
# 打印嵌入水印信息后幅度
# print(f"嵌入水印信息后幅度: {modified_amplitude}")
else:
modified_amplitude = f_amplitudes[x[i], y[i]]
# 重新构造复数值并更新频域数据
# f_shifted[x[i], y[i]] = modified_amplitude * np.exp(1j * phase[i])
f_shifted = f_amplitudes * np.exp(1j * f_phase)
# 频域图像转换回空间域
img_lowpass_multi_circular = np.real(ifft2(ifftshift(f_shifted)))
b_channel = np.clip(img_lowpass_multi_circular, 0, 255).astype(np.uint16)
# 合并修改后的通道回到一个新的图像数组中
# merged_img = np.stack((r_channel, g_channel, b_channel), axis=-1)
corrected_image[:, :, 2] = b_channel
final_image = Image.fromarray(corrected_image)
# 添加边框
final_image_copy = final_image.copy()
border_size = 30
# border_color = (73, 116, 165)
# 设置border_color 为rgb 白色
border_color = (255, 255, 255)
border = (border_size, border_size, border_size, border_size) # (左, 上, 右, 下)
img_with_border = ImageOps.expand(final_image_copy, border=border, fill=border_color)
# 将merged_img保存到文件文件是为当前时间的字符串
filename = f"{datetime.now().strftime('%Y%m%d%H%M%S')}.tiff"
# cv2.imwrite(filename, merged_img)
img_with_border.save(filename, format='TIFF')
# 显示结果
plt.figure(figsize=(12, 12))
plt.subplot(221)
plt.title("Original Image")
plt.imshow(img_copy, cmap='gray')
plt.subplot(222)
plt.title("Frequency Spectrum")
plt.imshow(np.log1p(np.abs(f_shifted)), cmap='gray')
plt.subplot(223)
plt.title("Multi Circular Lowpass Filter (50 Points per Circle)")
plt.imshow(frequency_domain_image, cmap='gray')
plt.subplot(224)
plt.title("Filtered Image (Multi Circular Low Frequencies, 50 Points per Circle)")
plt.imshow(final_image)
plt.tight_layout()
plt.show()
return filename
def validate_qr(filename, watermark_sequence):
watermarks = [int(watermark_sequence[i:i + 2]) for i in range(0, len(watermark_sequence), 2)]
# 根据 key读取数据库圆形信息
key = json.dumps(watermarks)
circles = []
# 查询第一条数据
qr = qrcode_data.select().where(qrcode_data.c.key == key)
result = connection.execute(qr).first()
if result:
# 将json字符串转换为列表
circles = json.loads(result[1])
print(circles)
# 验证水印结果
result, img2 = process_image_b_component(filename, circles, watermarks)
print(result)
else:
print("未找到数据")
# psnr = calculate_psnr(img_copy, img2)
# print(f"PSNR: {psnr}")
# # PSNR > 40 dB通常被认为是高质量图像
# # 30 dB < PSNR < 40 dB图像质量是良好
# if psnr > 40:
# print("图像质量是高质量")
# elif 35 <= psnr < 40:
# print("图像质量是良好")
# else:
# print("图像质量是差")
# img_path = './temp/tmpb_1ffsve.png'
# exec_image_result(img_path)
img_path = "corrected_image.png"
qr = "9918314391322762"
validate_qr(img_path, qr)
# perspective_transform(img_path)