You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用OpenCV对图像进行非均匀拉伸以实现1像素:1毫米比例

非圆形瓶身标签图像的精确展开方案

问题背景

现有贴在非圆形瓶身的标签图像,输入为直视瓶子的低视角图像,需将标签展开为1像素对应1毫米的精确比例。已通过瓶身曲率方程获取每列像素的压缩系数(例如第1741列对应0.773802毫米)。当前采用概率性代码通过随机复制/删除列进行拉伸校正,但存在以下问题:

  • 相同输入每次输出结果不同
  • 拉伸效果不均匀,无法保证精确比例

当前概率性实现代码

import random
import cv2
import numpy as np


def regularise_image_stretch(img: cv2.Mat, compensations: np.ndarray) -> cv2.Mat:
    '''基于列索引对应的压缩/拉伸系数,对图像进行非均匀拉伸,使像素:mm比例全局统一

    参数:
        img (cv2.Mat): 存在非均匀变形的输入图像
        compensations (np.ndarray): 由曲率方程生成的每列压缩系数数组

    返回:
        cv2.Mat: 满足1像素=1毫米的校正后图像
    '''
    def decision(val: float) -> tuple[str, bool, float]:
        '''根据压缩系数,用概率方法决定复制前一列或删除当前列

        参数:
            val (float): 当前列的压缩系数

        返回:
            tuple[str, bool, float]: 操作类型("add"/"rem"), 是否执行操作, 操作概率
        '''
        addrem = "rem"
        probability = 1 - val
        if probability > 0:
            addrem = "add"

        probability = abs(probability)

        return (addrem, random.random() < probability, probability)

    modimg = img.copy()

    res = list(map(decision, compensations))
    new_img = []
    previous_col = modimg[:, 0, :]

    # 根据压缩系数添加/复制列
    for i, col in enumerate(modimg.transpose(1,0,2)):
        addrem, shouldapply, _ = res[i]
        new_img.append(col)

        if shouldapply:
            if addrem == "add":
                new_img.append(previous_col)
            else:
                new_img.pop(-1)

        previous_col = col

    # 修正图像方向
    new_img = cv2.rotate(np.array(new_img), cv2.ROTATE_90_COUNTERCLOCKWISE)
    new_img = cv2.flip(np.array(new_img), 0)
    new_img = cv2.resize(new_img, (img.shape[1], img.shape[0]))
    return new_img


img = cv2.imread("./stripes.jpg")

new_img = regularise_image_stretch(img, compensations)
cv2.imwrite("./modifiend2.png", np.vstack([new_img, img]))

最优确定性解决方案

采用累积压缩系数映射+插值重采样的方法,完全避免随机性,保证输出一致且拉伸均匀,精确满足1像素=1毫米的要求。核心逻辑:

  1. 计算原图像每列的累积毫米数,得到原图像的总实际长度(即目标图像的宽度)
  2. 对目标图像的每个像素列,反向映射到原图像的累积毫米位置,通过插值获取像素值
  3. 保留原图像的高度,仅调整宽度方向的像素映射

实现代码

import cv2
import numpy as np


def precise_label_unwrap(img: cv2.Mat, compensations: np.ndarray) -> cv2.Mat:
    '''基于确定性映射,将非圆形瓶身标签图像精确展开为1像素=1毫米的比例

    参数:
        img (cv2.Mat): 存在曲率变形的输入图像
        compensations (np.ndarray): 每列像素对应的实际毫米数数组(压缩系数)

    返回:
        cv2.Mat: 精确校正后的图像
    '''
    # 计算累积毫米数,得到原图像各列对应的实际位置(从左到右的总毫米数)
    cumulative_mm = np.cumsum(compensations)
    total_mm = cumulative_mm[-1]
    target_width = int(np.round(total_mm))  # 目标宽度=总毫米数,1像素=1毫米
    target_height = img.shape[0]

    # 创建目标图像
    unwrapped_img = np.zeros((target_height, target_width, img.shape[2]), dtype=img.dtype)

    # 遍历目标图像的每一列(对应1毫米)
    for target_x in range(target_width):
        # 找到原图像中对应target_x毫米的位置
        idx = np.searchsorted(cumulative_mm, target_x, side='right') - 1
        if idx < 0:
            idx = 0
        elif idx >= len(cumulative_mm) - 1:
            idx = len(cumulative_mm) - 2

        # 计算插值权重
        prev_mm = cumulative_mm[idx]
        next_mm = cumulative_mm[idx + 1]
        weight = (target_x - prev_mm) / (next_mm - prev_mm) if next_mm != prev_mm else 0.0

        # 获取相邻两列的像素值,进行线性插值
        col_prev = img[:, idx, :].astype(np.float32)
        col_next = img[:, idx + 1, :].astype(np.float32)
        interpolated_col = (1 - weight) * col_prev + weight * col_next
        unwrapped_img[:, target_x, :] = interpolated_col.astype(img.dtype)

    return unwrapped_img


# 示例调用
img = cv2.imread("./stripes.jpg")
# 假设compensations是已计算好的每列毫米数数组
new_img = precise_label_unwrap(img, compensations)
cv2.imwrite("./unwrapped_precise.png", np.vstack([new_img, img]))

方案优势

  • 完全确定性:相同输入每次输出完全一致
  • 精确比例:严格保证1像素对应1毫米
  • 均匀拉伸:通过线性插值避免复制/删除列带来的块状失真
  • 鲁棒性强:适用于任意非圆形瓶身的曲率系数

内容的提问来源于stack exchange,提问作者Dara O h

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 10:05:39