[arc081f]Flip and Rectangles

本文介绍了一种算法,用于解决一个黑白网格图通过翻转行和列颜色来获得最大黑色子矩阵的问题。核心思路在于判断2×2子矩阵中黑色数量的奇偶性,并利用单调栈优化解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

一开始想如何染色成功。
发现只需要讨论四元环。
然后发现可以直接把规模缩成2*2矩阵。

题目大意

一个黑白网格图,你可以翻转任意行和任意列的颜色,求以此得到网格图最大黑色子矩阵面积。

做法

结论是一个矩阵如果任意一个2*2子矩阵都有偶数个黑色,一定可以通过翻转操作扭转为全黑。
易证必要充分。
然后就可以做经典问题。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=2000+10;
bool bz[maxn][maxn],pd[maxn][maxn];
int up[maxn][maxn],sta[maxn];
int i,j,k,l,t,n,m,ans,top;
char get(){
    char ch=getchar();
    while (ch!='#'&&ch!='.') ch=getchar();
    return ch;
}
int main(){
    scanf("%d%d",&n,&m);
    fo(i,1,n){
        fo(j,1,m)
            if (get()=='#') bz[i][j]=1;
    }
    fo(i,2,n)
        fo(j,2,m){
            t=(bz[i-1][j-1]+bz[i-1][j]+bz[i][j-1]+bz[i][j])%2;
            if (!t) pd[i-1][j-1]=1;
        }
    fo(i,1,n-1)
        fo(j,1,m-1) up[i][j]=pd[i][j]?up[i-1][j]+1:0;
    ans=max(n,m);
    fo(i,1,n-1){
        top=0;
        fo(j,1,m){
            while (top&&up[i][j]<=up[i][sta[top]]){
                ans=max(ans,(j-sta[top-1])*(up[i][sta[top]]+1));
                top--;
            }
            sta[++top]=j;
        }
    }
    printf("%d\n",ans);
}
import cv2 import numpy as np import platform from collections collections import defaultdict import time # 纸张物理参数(毫米) PAPER_WIDTH_MM = 170.0 PAPER_HEIGHT_MM = 257.0 BORDER_WIDTH_MM = 20.0 # 边框宽度 class PaperGeometryDetector: def __init__(self): # 3D参考点(纸张四角,以中心为原点) self.object_3d_points = np.array( [ [-PAPER_WIDTH_MM / 2, -PAPER_HEIGHT_MM / 2, 0], [PAPER_WIDTH_MM / 2, -PAPER_HEIGHT_MM / 2, 0], [PAPER_WIDTH_MM / 2, PAPER_HEIGHT_MM / 2, 0], [-PAPER_WIDTH_MM / 2, PAPER_HEIGHT_MM / 2, 0], ], dtype=np.float32, ) # 摄像头内参(可根据实际校准修改) self.camera_matrix = np.array( [[640, 0, 320], [0, 640, 240], [0, 0, 1]], dtype=np.float32 ) self.dist_coeffs = np.zeros((5, 1), dtype=np.float32) # 畸变系数 self.try_open_camera() self.frame_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) if hasattr(self, 'cap') else 640 self.frame_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) if hasattr(self, 'cap') else 480 # 调整内参以匹配实际分辨率 self.camera_matrix[0, 2] = self.frame_width / 2 self.camera_matrix[1, 2] = self.frame_height / 2 self.pixel_per_mm = 1.0 # 像素-毫米转换比例 self.distance = 0.0 # 摄像头到纸张的距离(毫米) self.edges = None self.finalWarped = None self.frame = None self.corners = None self.warped = None def try_open_camera(self, camera_id=0): """打开摄像头并设置分辨率""" if platform.system().lower() == "windows": cap = cv2.VideoCapture(camera_id, cv2.CAP_DSHOW) else: cap = cv2.VideoCapture(camera_id) # 设置分辨率(640x480兼容大多数摄像头) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) if cap.isOpened(): print(f"成功打开摄像头: {camera_id},分辨率: 640x480") self.cap = cap return True else: cap.release() print("未找到可用的摄像头") return False def detect_corners(self, frame): """检测纸张四角并排序""" def angle_sort(pt): vec = pt - center return np.arctan2(vec[1], vec[0]) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) ret, binary_img = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY_INV) # 反相阈值,突出白色纸张 edges = cv2.Canny(binary_img, 50, 150) contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) valid_contours = [] for cnt in contours: area = cv2.contourArea(cnt) if area < 5000: # 过滤小轮廓 continue epsilon = 0.02 * cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, epsilon, True) if len(approx) == 4 and cv2.isContourConvex(approx): # 四边形形且凸多边形 corners = approx.reshape(4, 2) valid_contours.append((area, corners)) if not valid_contours: return None # 选择面积最大的轮廓(纸张) valid_contours.sort(key=lambda x: x[0], reverse=True) largest_area, corners = valid_contours[0] # 按角度排序角点(确保顺序一致) center = corners.mean(axis=0) sorted_corners = sorted(corners, key=angle_sort) start_idx = np.argmin([pt[1] for pt in sorted_corners]) # 顶部角点为起点 ordered = np.roll(sorted_corners, -start_idx, axis=0) return np.array(ordered, dtype=np.float32) def order_points(self, pts): """按左上、右上、右下、左下排序点""" rect = np.zeros((4, 2), dtype=np.float32) s = pts.sum(axis=1) diff = np.diff(pts, axis=1) rect[0] = pts[np.argmin(s)] # 左上 rect[2] = pts[np.argmax(s)] # 右下 rect[1] = pts[np.argmin(diff)] # 右上 rect[3] = pts[np.argmax(diff)] # 左下 return rect def calculate_distance_pnp(self, image_points): """通过PNP算法计算摄像头到纸张的距离(毫米)""" if image_points is None or len(image_points) != 4: return None ordered_points = self.order_points(image_points) try: success, rvec, tvec = cv2.solvePnP( self.object_3d_points, ordered_points, self.camera_matrix, self.dist_coeffs ) if success: # 距离为平移向量的模长 distance = np.linalg.norm(tvec) return distance except Exception as e: print(f"PNP计算失败: {e}") return None def warp_perspective_from_corners(self, frame, corners, output_size=(int(PAPER_WIDTH_MM * 2), int(PAPER_HEIGHT_MM * 2))): """透视变换矫正纸张区域""" if corners is None or len(corners) != 4: return None, None, None rect = self.order_points(corners) dst = np.array([ [0, 0], [output_size[0] - 1, 0], [output_size[0] - 1, output_size[1] - 1], [0, output_size[1] - 1] ], dtype=np.float32) M = cv2.getPerspectiveTransform(rect, dst) Minv = cv2.getPerspectiveTransform(dst, rect) warped = cv2.warpPerspective(frame, M, output_size) return warped, M, Minv def resize_warped(self): """缩放矫正后的后的图像,计算像素-毫米比例""" up_points = (int(PAPER_WIDTH_MM * 3), int(PAPER_HEIGHT_MM * 3)) # 按纸张尺寸放大3倍 resized_up = cv2.resize(self.warped, up_points, interpolation=cv2.INTER_LINEAR) self.pixel_per_mm = up_points[0] / PAPER_WIDTH_MM # 像素/毫米比例(水平方向) return resized_up def pixels_to_mm(self, pixels): """像素转毫米""" return pixels / self.pixel_per_mm def mm_to_pixels(self, mm): """毫米转像素""" return mm * self.pixel_per_mm def run(self): """执行纸张检测和预处理""" if not hasattr(self, 'cap'): return False ret, frame = self.cap.read() if not ret: return False self.frame = cv2.flip(frame, 1) # 镜像翻转,便于操作 self.corners = self.detect_corners(self.frame) if self.corners is None: return False # 计算摄像头到纸张的距离(毫米) self.distance = self.calculate_distance_pnp(self.corners) if self.distance is None: return False # 透视变换并缩放 self.warped, M, Minv = self.warp_perspective_from_corners(self.frame, self.corners) if self.warped is None: return False self.finalWarped = self.resize_warped() # 边缘检测(用于后续形状识别) gray = cv2.cvtColor(self.finalWarped, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (3, 3), 0) ret, binary_img = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY_INV) self.edges = cv2.Canny(binary_img, 50, 150) # 遮盖边框区域(避免干扰) edge_border = int(self.mm_to_pixels(BORDER_WIDTH_MM)) + 5 self.edges[:edge_border, :] = 0 # 顶部 self.edges[-edge_border:, :] = 0 # 底部 self.edges[:, :edge_border] = 0 # 左侧 self.edges[:, -edge_border:] = 0 # 右侧 # 绘制角点和距离信息 for i, corner in enumerate(self.corners): cv2.circle(self.frame, tuple(corner.astype(int)), 5, (0, 255, 0), -1) cv2.putText(self.frame, f"Corner {i}", tuple(corner.astype(int) + 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) # 显示距离(转换为厘米) cv2.putText(self.frame, f"纸张距离: {self.distance / 10:.1f}cm", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) return True class ShapeDetector: """形状检测与尺寸测量(含矩形扩展与内部边框识别)""" def __init__(self, geo): self.geo = geo self.detection_history = defaultdict(list) # 存储检测历史 self.expand_pixels = 8 # 矩形向外扩展的像素数(可根据需要调整) self.inner_borders = [] # 存储检测到的内部边框 def detect_inner_borders(self, roi): """检测ROI区域内的内部边框""" # 对ROI区域进行边缘检测 gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) blurred_roi = cv2.GaussianBlur(gray_roi, (3, 3), 0) edges_roi = cv2.Canny(blurred_roi, 50, 150) # 查找内部轮廓 inner_contours, _ = cv2.findContours(edges_roi, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) inner_borders = [] for cnt in inner_contours: area = cv2.contourArea(cnt) if 50 < area < 10000: # 过滤过小或过大的轮廓 peri = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.02 * peri, True) if len(approx) == 4: # 只保留四边形边框 inner_borders.append(approx) return inner_borders def expand_rectangle(self, x, y, w, h, img_shape): """将矩形向外扩展指定像素,确保不超出图像边界""" x_expanded = max(0, x - self.expand_pixels) y_expanded = max(0, y - self.expand_pixels) w_expanded = min(img_shape[1] - x_expanded, w + 2 * self.expand_pixels) h_expanded = min(img_shape[0] - y_expanded, h + 2 * self.expand_pixels) return x_expanded, y_expanded, w_expanded, h_expanded def detect_shapes(self): """检测纸张上的形状并测量尺寸(增强矩形处理)""" if self.geo.edges is None or self.geo.finalWarped is None: return [] contours, _ = cv2.findContours(self.geo.edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) detected_shapes = [] img_shape = self.geo.finalWarped.shape self.inner_borders = [] # 重置内部边框列表 for cnt in contours: area_px = cv2.contourArea(cnt) if area_px < 100: # 过滤小轮廓 continue # 形状识别 peri = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.03 * peri, True) vertices = len(approx) shape = "unknown" size_mm = 0.0 # 三角形(3条边) if vertices == 3: shape = "triangle" # 计算边长(取平均值) sides = [] for i in range(3): p1 = approx[i][0] p2 = approx[(i + 1) % 3][0] side_px = np.linalg.norm(p1 - p2) sides.append(self.geo.pixels_to_mm(side_px)) size_mm = np.mean(sides) # 平均边长 # 四边形(4条边) elif vertices == 4: x, y, w, h = cv2.boundingRect(approx) aspect_ratio = w / h # 扩展矩形边界并检测内部边框 x_exp, y_exp, w_exp, h_exp = self.expand_rectangle(x, y, w, h, img_shape) roi = self.geo.finalWarped[y_exp:y_exp + h_exp, x_exp:x_exp + w_exp] inner_borders = self.detect_inner_borders(roi) # 存储内部边框(调整坐标到原始图像) for border in inner_borders: border_shifted = border + np.array([[x_exp, y_exp]], dtype=np.int32) self.inner_borders.append(border_shifted) # 区分正方形和矩形 if 0.95 <= aspect_ratio <= 1.05: shape = "square" else: shape = "rectangle" # 计算边长(取平均值) sides = [] for i in range(4): p1 = approx[i][0] p2 = approx[(i + 1) % 4][0] side_px = np.linalg.norm(p1 - p2) sides.append(self.geo.pixels_to_mm(side_px)) size_mm = np.mean(sides) # 平均边长 # 圆形(通过圆度判断) else: circularity = 4 * np.pi * area_px / (peri ** 2) if peri > 0 else 0 if 0.7 < circularity < 1.3: shape = "circle" # 计算直径(最小外接圆) (x, y), radius_px = cv2.minEnclosingCircle(cnt) size_mm = self.geo.pixels_to_mm(radius_px * 2) # 直径 # 只保留有效形状 if shape in ["triangle", "square", "rectangle", "circle"]: detected_shapes.append((shape, size_mm, cnt)) # 记录历史数据(用于后续平均计算) self.detection_history[shape].append(size_mm) return detected_shapes def draw_detections(self, shapes): """在图像上绘制检测结果""" if self.geo.finalWarped is None: return # 绘制形状 for shape, size_mm, cnt in shapes: # 绘制轮廓 cv2.drawContours(self.geo.finalWarped, [cnt], -1, (0, 255, 0), 2) # 计算中心坐标 M = cv2.moments(cnt) if M["m00"] == 0: continue cX = int(M["m10"] / M["m00"]) cY = int(M["m01"] / M["m00"]) # 显示形状和尺寸 label = f"{shape}: {size_mm:.1f}mm" if shape == "circle": label = f"{shape} (直径): {size_mm:.1f}mm" cv2.putText(self.geo.finalWarped, label, (cX - 50, cY), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2) # 绘制扩展区域和内部边框 for border in self.inner_borders: cv2.drawContours(self.geo.finalWarped, [border], -1, (255, 0, 0), 2) # 蓝色框标记内部边框 def print_stats(self, duration=3): """打印指定时间内的检测统计结果""" print(f"\n===== {duration}秒检测统计 =====") print(f"纸张尺寸: {PAPER_WIDTH_MM}mm x {PAPER_HEIGHT_MM}mm") print(f"摄像头到纸张距离: {self.geo.distance / 10:.1f}cm") if not self.detection_history: print("未检测到有效形状") return for shape, sizes in self.detection_history.items(): avg_size = np.mean(sizes) print(f"\n{shape}:") print(f" 平均尺寸: {avg_size:.1f}mm") print(f" 检测次数: {len(sizes)}") print("================================\n") def main(): geo = PaperGeometryDetector() if not hasattr(geo, 'cap'): # 检查摄像头是否成功打开 return shape_detector = ShapeDetector(geo) detection_duration = 3 # 统计时长(秒) start_time = time.time() print(f"程序启动,将在{detection_duration}秒后输出统计结果(按'q'退出)") while True: if cv2.waitKey(1) & 0xFF == ord('q'): break # 执行纸张检测 success = geo.run() if hasattr(geo, 'run') else False if not success: # 显示未检测到纸张的提示 if hasattr(geo, 'frame') and geo.frame is not None: cv2.putText(geo.frame, "未检测到纸张", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) cv2.imshow("原始图像", geo.frame) continue # 检测形状并绘制结果 shapes = shape_detector.detect_shapes() shape_detector.draw_detections(shapes) # 显示图像 cv2.imshow("原始图像", geo.frame) cv2.imshow("矫正后纸张", geo.finalWarped) cv2.imshow("边缘检测", geo.edges) # 超时后输出统计结果并重置 if time.time() - start_time >= detection_duration: shape_detector.print_stats(detection_duration) shape_detector.detection_history.clear() # 清空历史数据 start_time = time.time() # 释放资源 if hasattr(geo, 'cap'): geo.cap.release() cv2.destroyAllWindows() print("程序已退出") if __name__ == "__main__": main()
最新发布
08-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值