class ImageService:
    """Service for image manipulations."""
    @staticmethod
    def get_binary_image(image: np.ndarray) -> np.ndarray:
        """Gets binary image from image."""
        binary_image = image.copy()
        binary_image[np.where(binary_image != 0)] = 255
        return binary_image

    @staticmethod
    def count_zero_pixels(image_gray: np.ndarray) -> int:
        """Counts pixels with 0 intensity on black-white image."""
        zero_pixels = np.count_nonzero(image_gray == 0)
        return zero_pixels

    @staticmethod
    def calculate_zero_pixel_percentage(image: np.ndarray) -> float:
        """Calculates zero intensity pixel's percentage."""
        total_pixels = image.size
        zero_pixels = ImageService.count_zero_pixels(image)
        zero_percentage = zero_pixels / total_pixels * 100
        return zero_percentage


class TimeService:
    """Service for time management."""
    def __init__(self, time_max: float):
        self.time_max = time_max
        self.start_time = None

    def start_timer(self) -> None:
        """Start time point. Sets 'self.start_time'."""
        self.start_time = time.time()

    def check_timer(self):
        """Checks if time more than 'self.time_max'."""
        end_time = time.time()
        delta_time = end_time - self.start_time
        if delta_time > self.time_max:
            return False
        return delta_time

POTENTIAL_POINTS_NUM = 200
ITERATIONS_NUM = 1000
THRESHOLD_DISTANCE = 3
THRESHOLD_K = 0.5
THRESHOLD_B = 20
NOISE_LESS = 75
IMAGE_THRESHOLD_MIN = 0
IMAGE_THRESHOLD_MAX = 100
TIME_MAX = 1.8

class CornerDetector(object):
    """Class for corner detection on image."""
    def __init__(
            self,
            potential_points_num: int = POTENTIAL_POINTS_NUM,
            iterations_num: int = ITERATIONS_NUM,
            threshold_distance: int = THRESHOLD_DISTANCE,
            threshold_k: float = THRESHOLD_K,
            threshold_b: int = THRESHOLD_B
    ):
        self.potential_points_num = potential_points_num
        self.iterations_num = iterations_num
        self.threshold_distance = threshold_distance
        self.threshold_k = threshold_k
        self.threshold_b = threshold_b
        self.image = None
        self.image_threshold = None
        self.time_service = TimeService(TIME_MAX)
        self.random_answer = np.array([[1, 1], [2, 2], [3, 3]])

    def __get_coords_by_threshold(
            self,
            image: np.ndarray,
    ) -> tuple:
        """Gets coordinates with intensity more than 'image_threshold'."""
        coords = np.where(image > self.image_threshold)
        return coords

    def __get_neigh_nums_list(
            self,
            coords: tuple,
            kernel_size: int = 3
    ) -> list:
        """
        Gets list with number of neighborhoods by kernel.
        Returns list like [[x1, y1, N1], [x2, y2, N2]],
        Where N - neighborhood's number.
        """
        neigh_nums = []
        offset = (kernel_size - 1) // 2

        for x, y in zip(coords[0], coords[1]):
            if x < 0 + offset or x > self.image.shape[0] - offset:
                continue
            if y < 0 + offset or y > self.image.shape[1] - offset:
                continue

            kernel = self.image[x - offset:x + offset + 1,
                     y - offset:y + offset + 1]
            step_neigh_num = np.count_nonzero(kernel) - 1
            neigh_nums.append([x, y, step_neigh_num])

        return neigh_nums

    @staticmethod
    def __sort_neigh_nums_by_N(
            neigh_nums: list
    ) -> np.ndarray:
        """
        Sort neigh_nums by N parameter.
        Removes N=-1, N=0 and sorts by N desc.
        """
        np_neigh_nums = np.array(neigh_nums)
        np_neigh_nums = np_neigh_nums[np_neigh_nums[:, 2] != 0]
        sorted_indices = np.argsort(-np_neigh_nums[:, 2])
        np_neigh_nums = np_neigh_nums[sorted_indices]
        return np_neigh_nums

    def __get_potential_points_coords(
            self,
            sorted_niegh_nums: np.ndarray
    ) -> np.ndarray:
        """
        Gets best candidates, potential points coords only.
        Returns np.ndarray like [[x1, y1], [x2, y2]].
        """
        sorted_neigh_nums = np.delete(sorted_niegh_nums, 2, axis=1)
        return sorted_neigh_nums[:self.potential_points_num]

    def __get_best_lines(
            self,
            potential_points: np.ndarray
    ) -> np.ndarray:
        """
        Gets the best combinations of lines by all points.
        Line y = kx + b.
        Returns np.ndarray like [[k1, b1], [k2, b2]].
        """
        remaining_points = 0
        best_lines = []
        num_lines = 0

        while num_lines < 3:
            if not self.time_service.check_timer():
                return self.random_answer
            if num_lines == 0:
                remaining_points = np.copy(potential_points)
            print(f"remaining points: {len(remaining_points)}")
            best_inlines = []
            best_line = None

            if len(remaining_points) == 0:
                remaining_points = np.array([[1, 1], [2, 2], [3, 3], [4, 4]])

            for i in range(self.iterations_num):
                sample_indices = np.random.choice(
                    remaining_points.shape[0],
                    size=(self.iterations_num, 3), replace=True)
                sample_points = remaining_points[sample_indices]
                sample_x = sample_points[:, 1]
                sample_y = sample_points[:, 0]
                coefficients = np.polyfit(sample_x, sample_y, deg=1)
                distances = np.abs(remaining_points[:, 0] - np.matmul(np.vstack((remaining_points[:, 1], np.ones_like(remaining_points[:, 1]))).T, coefficients)) / np.sqrt(coefficients[0] ** 2 + 1)
                inlines = np.where(distances < self.threshold_distance)[0]

                is_similar = False
                for line in best_lines:
                    diff_k = abs(coefficients[0] - line[0])
                    diff_b = abs(coefficients[1] - line[1])

                    if diff_k <= self.threshold_k and diff_b <= self.threshold_b:
                        is_similar = True
                        break

                if not is_similar and len(inlines) > len(best_inlines):
                    best_line = coefficients
                    best_inlines = inlines

            if best_line is not None:
                best_lines.append(best_line)
                remaining_points = np.delete(remaining_points, best_inlines, axis=0)

            num_lines += 1

        return np.array(best_lines)

    def find_corners(self, image: np.ndarray) -> np.ndarray:
        """Finding triangle corners on image process."""
        self.time_service.start_timer()
        self.image = image

        zero_percentage = ImageService.calculate_zero_pixel_percentage(image)

        if zero_percentage > NOISE_LESS:
            self.image_threshold = IMAGE_THRESHOLD_MIN
        else:
            self.image_threshold = IMAGE_THRESHOLD_MAX

        # Noise i cannot realise.
        if zero_percentage < 45:
            return self.random_answer

        coords = self.__get_coords_by_threshold(image)
        if not self.time_service.check_timer():
            return self.random_answer

        neigh_nums = self.__get_neigh_nums_list(coords)
        if not self.time_service.check_timer():
            return self.random_answer

        sorted_neigh_nums = self.__sort_neigh_nums_by_N(neigh_nums)
        if not self.time_service.check_timer():
            return self.random_answer

        potential_points = self.__get_potential_points_coords(sorted_neigh_nums)
        if not self.time_service.check_timer():
            return self.random_answer

        best_lines = self.__get_best_lines(potential_points)
        if not self.time_service.check_timer():
            return self.random_answer

        intersection_points = np.linalg.solve(
            np.array([[best_lines[0][0], -1], [best_lines[1][0], -1]]),
            [-best_lines[0][1], -best_lines[1][1]])
        intersection_points = np.vstack((intersection_points, np.linalg.solve(
            np.array([[best_lines[0][0], -1], [best_lines[2][0], -1]]),
            [-best_lines[0][1], -best_lines[2][1]])))
        intersection_points = np.vstack((intersection_points, np.linalg.solve(
            np.array([[best_lines[1][0], -1], [best_lines[2][0], -1]]),
            [-best_lines[1][1], -best_lines[2][1]])))

        return intersection_points

class Solver(object):
    def __init__(self):
        self.alg = CornerDetector()

    def solve(self, image_gray: np.ndarray) -> np.ndarray:
        result = self.alg.find_corners(image_gray)
        return result

image = cv2.imread("0.3/02.pgm", cv2.COLOR_BGR2GRAY)
solver = Solver()
solver.solve(image)

ПОЛУЧАЮ ВОТ ТАКУЮ ОШИБКУ:
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-45-4c8a0c34fe22> in <cell line: 11>()
      9 image = cv2.imread("0.3/02.pgm", cv2.COLOR_BGR2GRAY)
     10 solver = Solver()
---> 11 solver.solve(image)

4 frames
/usr/local/lib/python3.10/dist-packages/numpy/lib/polynomial.py in polyfit(x, y, deg, rcond, full, w, cov)
    634         raise ValueError("expected deg >= 0")
    635     if x.ndim != 1:
--> 636         raise TypeError("expected 1D vector for x")
    637     if x.size == 0:
    638         raise TypeError("expected non-empty vector for x")

TypeError: expected 1D vector for x
ЧТО ДЕЛАТЬ?!?! ИСПРАВЬ!!!