如何计算显示整个旋转图像所需的零矩阵的大小?

问题描述 投票:0回答:1

我正在尝试编写一个Python程序,将选定的“图像处理”操作应用于加载的图像,而不使用cv2库中的现成函数。下面是图像旋转函数:

# * Python version:  3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)]
# * OpenCV version:  4.9.0
# * Numpy version:   1.26.4
# * PyQt5 version:   5.15.10

import sys
from PyQt5.QtWidgets import QApplication, QTextEdit, QWidget, QVBoxLayout, QPushButton, QComboBox, QLabel, QFileDialog, QInputDialog
from PyQt5.QtGui import QPixmap, QImage, QColor
import cv2
import numpy as np

red_color = QColor(204, 0, 0)
green_color = QColor(0, 153, 0)
blue_color = QColor(0, 102, 255)
black_color = QColor(0, 0, 0)

class App(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.image = None

    def initUI(self):
        self.layout = QVBoxLayout()

        self.terminal_codes = QTextEdit(self)
        self.terminal_codes.setReadOnly(True)
        self.layout.addWidget(self.terminal_codes)
        self.terminal_codes.setFixedSize(400,150)
        
        self.upload_button = QPushButton('Upload Image', self)
        self.upload_button.clicked.connect(self.upload_image)
        self.layout.addWidget(self.upload_button)

        self.operation_combo = QComboBox(self)
        self.operation_combo.addItem('Select Operation')
        self.operation_combo.addItem('Image Rotation')
        
        self.layout.addWidget(self.operation_combo)

        self.apply_button = QPushButton('Apply and Download', self)
        self.apply_button.clicked.connect(self.apply_operation)
        self.layout.addWidget(self.apply_button)

        self.setLayout(self.layout)

        self.show()
        self.terminal_codes.setTextColor(green_color)
        self.terminal_codes.append("Program is ready.")
        
        self.operation_combo.currentIndexChanged.connect(self.update_terminal_codes)
        
    def update_terminal_codes(self, index):
        # Update terminal codes function
            
    def upload_image(self):
        # Upload image function
    
    def apply_operation(self):
        
        operation = self.operation_combo.currentText()

        if operation == 'Image Rotation':
            angle, ok = QInputDialog.getDouble(self, 'Image Rotation', 'Enter angle:')
            if ok:
                rotated_image = self.rotate_image(self.image, angle)
                self.download_image(rotated_image)
                self.terminal_codes.clear()
                self.terminal_codes.setTextColor(green_color)
                self.terminal_codes.append("Rotation angle:{}\nImage Rotation operation was applied.".format(angle))
    
    def download_image(self, image):
        # Download image function

    def rotate_image(self, image, angle):
        (h, w) = image.shape[:2]
        center = (w // 2, h // 2)
        rotated = np.zeros((h, w, 3), dtype=np.uint8)
        for i in range(h):
            for j in range(w):
                new_x = (j - center[0]) * np.cos(np.deg2rad(angle)) + (i - center[1]) * np.sin(np.deg2rad(angle)) + center[0]
                new_y = -(j - center[0]) * np.sin(np.deg2rad(angle)) + (i - center[1]) * np.cos(np.deg2rad(angle)) + center[1]
                if new_x >= 0 and new_x < h and new_y >= 0 and new_y < w:
                    x0 = int(np.floor(new_x))
                    y0 = int(np.floor(new_y))
                    x1 = int(np.ceil(new_x))
                    y1 = int(np.ceil(new_y))
                    if x0 < 0:
                        x0 = 0
                    if y0 < 0:
                        y0 = 0
                    if x1 >= w:
                        x1 = w - 1
                    if y1 >= h:
                        y1 = h - 1
                    a = new_x - x0
                    b = new_y - y0
                    c = 1 - a
                    d = 1 - b
                    for channel in range(3):
                        rotated[i, j, channel] = (c * d * image[y0, x0, channel] + a * d * image[y0, x1, channel] + c * b * image[y1, x0, channel] + a * b * image[y1, x1, channel])
        return rotated

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

rotate_image函数的工作原理如下:

  • 使用
    h
    w
    center
    ,我们计算图像的高度、宽度和中心。
  • 使用
    rotated = np.zeros((h, w, 3), dtype=np.uint8)
    我们创建一个空矩阵来放置结果图像。
  • 通过嵌套 (i, j) for 循环中的
    new_x
    new_y
    ,我们计算旋转后每个像素的新位置。
  • 通过查询
    new_x >= 0 and new_x < h and new_y >= 0 and new_y < w
    ,我们检查新的像素位置是否在图像边界内。如果不是,我们忽略该像素。如果满足条件,我们就进行剩下的操作。我把剩下的操作口授给人工智能,因为我不知道该怎么做。 AI的解释如下:“这部分函数执行双线性插值来计算新的像素值。它找到距离新位置最近的四个像素,并将新的像素值计算为这四个像素的加权平均值。 ”
  • 最后我们返回旋转后的图像。

我的问题是我无法显示整个返回的图像,因为查询

new_x >= 0 and new_x < h and new_y >= 0 and new_y < w
忽略了边界之外的像素。结果图像: enter image description here

我想我需要重写代码

rotated = np.zeros((h, w, 3), dtype=np.uint8)
来解决这个问题。但我不知道如何以数学方式表达旋转图像适合的矩阵。我是说: enter image description here

上图中,黑色边框代表图像旋转后需要适应的空间。

由于我的数学知识不够,我不知道如何使用图像的旋转角度、高度、宽度(旋转图像的边界框)来计算该图像可以拟合的零矩阵。我需要这方面的帮助。

python numpy opencv math matrix
1个回答
0
投票

这是有效的。

code00.py

#!/usr/bin/env python

import sys

import cv2
import numpy as np


# @TODO - cfati: Method from question
def rotate_image(image, angle):
    (h, w) = image.shape[:2]
    center = (w // 2, h // 2)
    rotated = np.zeros((h, w, 3), dtype=np.uint8)
    for i in range(h):
        for j in range(w):
            new_x = (j - center[0]) * np.cos(np.deg2rad(angle)) + (i - center[1]) * np.sin(np.deg2rad(angle)) + center[0]
            new_y = -(j - center[0]) * np.sin(np.deg2rad(angle)) + (i - center[1]) * np.cos(np.deg2rad(angle)) + center[1]
            if new_x >= 0 and new_x < h and new_y >= 0 and new_y < w:
                x0 = int(np.floor(new_x))
                y0 = int(np.floor(new_y))
                x1 = int(np.ceil(new_x))
                y1 = int(np.ceil(new_y))
                if x0 < 0:
                    x0 = 0
                if y0 < 0:
                    y0 = 0
                if x1 >= w:
                    x1 = w - 1
                if y1 >= h:
                    y1 = h - 1
                a = new_x - x0
                b = new_y - y0
                c = 1 - a
                d = 1 - b
                for channel in range(3):
                    rotated[i, j, channel] = (c * d * image[y0, x0, channel] + a * d * image[y0, x1, channel] + c * b * image[y1, x0, channel] + a * b * image[y1, x1, channel])
    return rotated


def rotate_point(x, y, xc, yc, cos_, sin_):
    return (x - xc) * cos_ - (y - yc) * sin_, (y - yc) * cos_ + (x - xc) * sin_


def rotate(img, alpha, original_size=False):
    h0, w0 = img.shape[:2]
    xc0 = w0 / 2
    yc0 = h0 / 2
    if original_size:
        w1, h1 = w0, h0
        xc1, yc1 = xc0, yc0
    else:
        cos = np.cos(np.deg2rad(alpha))
        sin = np.sin(np.deg2rad(alpha))
        xtl, ytl = rotate_point(0, 0, xc0, yc0, cos, sin)
        xtr, ytr = rotate_point(w0, 0, xc0, yc0, cos, sin)
        xbl, ybl = rotate_point(0, h0, xc0, yc0, cos, sin)
        xbr, ybr = rotate_point(w0, h0, xc0, yc0, cos, sin)

        xs = (xtl, xtr, xbl, xbr)
        ys = (ytl, ytr, ybl, ybr)

        w1, h1 = round(max(xs) - min(xs)), round(max(ys) - min(ys))
        xc1, yc1 = w1 / 2, h1 / 2

    cosr = np.cos(np.deg2rad(-alpha))
    sinr = np.sin(np.deg2rad(-alpha))

    ret = np.zeros((h1, w1, img.shape[2]), dtype=img.dtype)
    for y1 in range(h1):
        for x1 in range(w1):
            x0, y0 = rotate_point(x1, y1, xc1, yc1, cosr, sinr)
            x0 = round(x0 + xc0)
            y0 = round(y0 + yc0)
            if 0 <= x0 < w0 and 0 <= y0 < h0:
                ret[y1, x1] = img[y0, x0]
    return ret


def main(*argv):
    orig = cv2.imread("img.png")
    angle = int(input("Enter angle (degrees): "))
    cv2.imshow(f"Original: {orig.shape[1]}x{orig.shape[0]}", orig)
    # Original function raises IndexError
    #rot0 = rotate_image(orig, angle)
    #cv2.imshow(f"Rotated question ({angle}): {rot0.shape[1]}x{rot0.shape[0]}", rot0)
    rot1 = rotate(orig, angle, original_size=True)
    cv2.imshow(f"Rotated ({angle}, same size): {rot1.shape[1]}x{rot1.shape[0]}", rot1)
    rot2 = rotate(orig, angle, original_size=False)
    cv2.imshow(f"Rotated ({angle}, enlarged): {rot2.shape[1]}x{rot2.shape[0]}", rot2)
    print("Press a key on any drawing window to exit...")
    cv2.waitKey(delay=0)


if __name__ == "__main__":
    print(
        "Python {:s} {:03d}bit on {:s}\n".format(
            " ".join(elem.strip() for elem in sys.version.split("\n")),
            64 if sys.maxsize > 0x100000000 else 32,
            sys.platform,
        )
    )
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

注释

  • 正如代码中所指定的,问题中的旋转功能不起作用(至少对于该图像)

  • 实现仅使用简单的转换(没有什么聪明之处)

  • 在性能方面,我仅“缓存”三角函数结果(以避免为每个点计算它们)。有一些可以改进的地方:

    • 摆脱rotate_point(“内联”其代码)

    • 使用(更快)NumPy例程

输出

© www.soinside.com 2019 - 2024. All rights reserved.