用opencv简单的检测三角形、正方形、圆以及它们的颜色

源码下载地址点击打开链接

原始图片:


检测结果:


检测后图片:



下面为完整代码:

#include <iostream>
#include "opencv2/opencv.hpp"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv/cv.h>
#include <opencv/highgui.h>

using namespace cv;
using namespace std;


double angle(Point pt1, Point pt2, Point pt0)
{
double dx1 = pt1.x - pt0.x;
double dy1 = pt1.y - pt0.y;
double dx2 = pt2.x - pt0.x;
double dy2 = pt2.y - pt0.y;

double cosine = (dx1*dx2 + dy1*dy2) / (sqrt(dx1*dx1 + dy1*dy1) * sqrt(dx2*dx2 + dy2*dy2) + 1e-10);


return cosine;
}

enum string{ 红, 橙, 黄, 绿, 青, 蓝, 紫, 黑, 白, 其他 }colors;


void  get_point(vector<Point > contour,Point *p){
if (contour.size() == 0)
return ;
int totalx = 0;
int totaly = 0;
int n = 0;
for (size_t i = 0; i < contour.size(); i++){
n++;
totalx = totalx + contour[i].x;
totaly = totaly + contour[i].y;
}
(*p).x = totalx / n;
(*p).y = totaly / n;
}

int detect_color(Point p, Mat & src, Mat & rgb)
{
int res=-1;
int h=src.at<Vec3b>(p.y, p.x)[0];
int s = src.at<Vec3b>(p.y, p.x)[1];
int v = src.at<Vec3b>(p.y, p.x)[2];

if (h >=0 && h <= 10 && s > 43  && v > 46 ){
res = 0;
}
else if (h >= 156 && h <= 180 && s > 43 && v > 46){
res = 0;
}
else if (h >= 11 && h <= 25 && s > 43 && v > 46){
res = 1;
}
else if (h >= 26 && h <= 34 && s > 43 && v > 46){
res = 2;
}
else if (h >= 35 && h <= 77 && s > 43 && v > 46){
res = 3;
}
else if (h >= 78 && h <= 99 && s > 43 && v > 46){
res = 4;
}
else if (h >= 100 && h <= 124 && s > 43 && v > 46){
res = 5;
}
else if (h >= 125 && h <= 155 && s > 43 && v > 46){
res = 6;
}
else 
{
int g = rgb.at<Vec3b>(p.y, p.x)[0];
int b = rgb.at<Vec3b>(p.y, p.x)[1];
int r = rgb.at<Vec3b>(p.y, p.x)[2];
if (g <=10 && b <= 10 && r <=10){
res = 7;
}
else if (g >=200 && b>=200 && r>=200){
res = 8;
}
else{
res = 9;
}
}
return res;
}

void shape_rgb(Mat & src, Mat & rgb,CvSeq *first_contour, int *num_squares, vector<int>& squares_color, vector<vector<Point>>& squares, vector<vector<Point>>& circles, int *num_circles, vector<int>& circles_color, vector<vector<Point>>& triangles, int *num_triangles, vector<int>& triangles_color){
vector<Point > contour;
vector<Point > approx;
Point poi = 0;
int nu = -1;
int cnt = 0;
for (; first_contour != 0; first_contour = first_contour->h_next)
{
vector <Point>().swap(contour);
vector <Point>().swap(approx);
cnt++;
for (int i = 0; i<first_contour->total; ++i) 
{
Point *p = (Point *)cvGetSeqElem(first_contour, i);
Point pp = *p;
contour.push_back(pp);
}

approxPolyDP(contour, approx, arcLength(contour, true)*0.02, true);
squares.push_back(approx);

if (approx.size() == 4 &&
fabs(contourArea(approx)) >= 1000 &&
isContourConvex(approx))
{
double maxCosine = 0;
for (int j = 2; j < 5; j++)
{
double cosine = fabs(angle(approx[j % 4], approx[j - 2], approx[j - 1]));
maxCosine = MAX(maxCosine, cosine);
}
if (maxCosine <= 0.3)             //直角的矩形应该为0
{
squares.push_back(approx);
*num_squares = *num_squares + 1;
nu = -1;
get_point(approx, &poi);
int nu = detect_color(poi, src, rgb);
squares_color.push_back(nu);
}
//re = re + 1;
}
else if (approx.size() == 3 &&fabs(contourArea(approx)) >= 500 &&isContourConvex(approx)){ //tt = tt + 1; //
triangles.push_back(approx);
*num_triangles = *num_triangles + 1;
nu = -1;
get_point(approx, &poi);
int nu = detect_color(poi, src, rgb);
triangles_color.push_back(nu);
}
else if (approx.size() >=8 && fabs(contourArea(approx)) >= 500 && isContourConvex(approx)){ //tt = tt + 1; //
circles.push_back(approx);
*num_circles = *num_circles + 1;
nu = -1;
get_point(approx, &poi);
int nu = detect_color(poi, src, rgb);
circles_color.push_back(nu);
}
}
}

void drawSquares(Mat img, vector<vector<Point>> squares)
{
polylines(img, squares, true, Scalar(0, 0, 0), 1, LINE_AA);

}


int main()
{
IplImage *src = cvLoadImage("dect.jpg", 1);
IplImage* dst = cvCreateImage(cvGetSize(src), 8, 3);
IplImage* dst1 = cvCreateImage(cvGetSize(src), 8, 1);
IplImage* color_dst = cvCreateImage(cvGetSize(src), 8, 3);
CvMemStorage* storage = cvCreateMemStorage(0);
CvMemStorage* Rstorage = cvCreateMemStorage(0);
CvMemStorage* Gstorage = cvCreateMemStorage(0);
CvMemStorage* Bstorage = cvCreateMemStorage(0);
CvSeq* lines = 0;
int i;
IplImage* src1 = cvCreateImage(cvSize(src->width, src->height), IPL_DEPTH_8U, 1);
IplImage*  RedImage = cvCreateImage(cvGetSize(src), 8, 1);
IplImage*  GreenImage = cvCreateImage(cvGetSize(src), 8, 1);
IplImage*  BlueImage = cvCreateImage(cvGetSize(src), 8, 1);
IplImage*  Rgray = cvCreateImage(cvGetSize(src), 8, 1);
IplImage*  Ggray = cvCreateImage(cvGetSize(src), 8, 1);
IplImage*  Bgray = cvCreateImage(cvGetSize(src), 8, 1);

cvSplit(src, BlueImage, GreenImage, RedImage, 0);
cvThreshold(BlueImage, Bgray, 100, 255, CV_THRESH_BINARY);
cvThreshold(GreenImage, Ggray, 100, 255, CV_THRESH_BINARY);
cvThreshold(RedImage, Rgray, 100, 255, CV_THRESH_BINARY);

vector<vector<Point>> contours;

CvZero(dst);
CvSeq *first_contour = NULL;
CvSeq *Rfirst_contour = NULL;
CvSeq *Gfirst_contour = NULL;
CvSeq *Bfirst_contour = NULL;

cvFindContours(Rgray, storage, &first_contour, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
for (; first_contour != 0; first_contour = first_contour->h_next)
{
if (first_contour->h_next = 0)
break;
cvDrawContours(dst, first_contour, CV_RGB(255, 255, 255), CV_RGB(255, 255, 255), 2, CV_FILLED, 8, cvPoint(0, 0));
}
cvCvtColor(dst, dst1, CV_BGR2GRAY);

cvFindContours(Rgray, Gstorage, &Gfirst_contour, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
cvFindContours(Ggray, Rstorage, &Rfirst_contour, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
cvFindContours(Bgray, Bstorage, &Bfirst_contour, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
cvZero(Rgray);
cvZero(Bgray);
cvZero(Ggray);

cvDrawContours(Rgray, Rfirst_contour, 255, 255, 1, CV_FILLED, 8, cvPoint(0, 0));
cvDrawContours(Ggray, Gfirst_contour, 255, 255, 1, CV_FILLED, 8, cvPoint(0, 0));
cvDrawContours(Bgray, Bfirst_contour, 255,255, 1, CV_FILLED, 8, cvPoint(0, 0));

cvMerge(Bgray, Ggray, Rgray,0,dst);
cvCvtColor(dst, dst1, CV_BGR2GRAY);
cvThreshold(dst1, dst1, 240, 255, CV_THRESH_BINARY_INV);
cvFindContours(dst1, storage, &first_contour, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

int num_squares=0;
vector<int> squares_color;
vector<vector<Point>> squares;
vector<vector<Point>> circles;
int num_circles=0; 
vector<int> circles_color;
vector<vector<Point>> triangles;
int num_triangles = 0;
vector<int> triangles_color;
Mat img;
img = cvarrToMat(src);
Mat imgHSV;

cvtColor(img, imgHSV, COLOR_BGR2HSV); //Convert the captured frame from BGR to HSV  
shape_rgb(imgHSV, img, first_contour, &num_squares, squares_color, squares, circles, &num_circles, circles_color, triangles, &num_triangles, triangles_color);


vector<int> mycircles_color;
IplImage* gray = cvCreateImage(cvGetSize(src), 8, 1);


CvMemStorage* mystorage = cvCreateMemStorage(0);
cvCvtColor(src, gray, CV_BGR2GRAY);
cvSmooth(gray, gray, CV_GAUSSIAN, 9, 9); // smooth it, otherwise a lot of false circles may be detected
CvSeq* mycircles = cvHoughCircles(gray, mystorage, CV_HOUGH_GRADIENT, 2, gray->height / 4, 200, 100);
Point circl = 0;
for (i = 0; i < mycircles->total; i++)
{
float* p = (float*)cvGetSeqElem(mycircles, i);
cvCircle(src, cvPoint(cvRound(p[0]), cvRound(p[1])), 3, CV_RGB(0, 255, 0), -1, 8, 0);
cvCircle(src, cvPoint(cvRound(p[0]), cvRound(p[1])), cvRound(p[2]), CV_RGB(255, 0, 0), 3, 8, 0);
circl.y = p[1];
circl.x = p[0];
int nu = detect_color(circl, imgHSV, img);
mycircles_color.push_back(nu);
}
drawSquares(img, squares);

printf("矩形数量:");
printf("%d", squares_color.size());
printf("\n");
printf("矩形颜色:");
printf("\n");
for (size_t i = 0; i < squares_color.size(); i++){
switch (squares_color[i])//判断枚举变量day的值  
{
case 0:printf("红\t"); break;
case 1:printf("橙\t"); break;
case 2:printf("黄\t"); break;
case 3:printf("绿\t"); break;
case 4:printf("青\t"); break;
case 5:printf("蓝\t"); break;
case 6:printf("紫\t"); break;
case 7:printf("黑\t"); break;
case 8:printf("白\t"); break;
case 9:printf("其他\t"); break;
}
//printf("\n");
}
printf("\n");
printf("圆形数量:");
printf("%d", num_circles);
printf("\n");
printf("圆形颜色:");
printf("\n");
for (size_t i = 0; i < circles_color.size(); i++){
switch (circles_color[i])//判断枚举变量day的值  
{
case 0:printf("红\t"); break;
case 1:printf("橙\t"); break;
case 2:printf("黄\t"); break;
case 3:printf("绿\t"); break;
case 4:printf("青\t"); break;
case 5:printf("蓝\t"); break;
case 6:printf("紫\t"); break;
case 7:printf("黑\t"); break;
case 8:printf("白\t"); break;
case 9:printf("其他\t"); break;
}
}
printf("\n");
printf("三角形数量:");
printf("%d", num_triangles);
printf("\n");
printf("三角形颜色:");
printf("\n");
for (size_t i = 0; i < triangles_color.size(); i++){
switch (triangles_color[i])//判断枚举变量day的值  
{
case 0:printf("红\t"); break;
case 1:printf("橙\t"); break;
case 2:printf("黄\t"); break;
case 3:printf("绿\t"); break;
case 4:printf("青\t"); break;
case 5:printf("蓝\t"); break;
case 6:printf("紫\t"); break;
case 7:printf("黑\t"); break;
case 8:printf("白\t"); break;
case 9:printf("其他\t"); break;
}
}
printf("\n");

cvNamedWindow("原始图片", 1);
cvShowImage("原始图片", dst);
cvNamedWindow("检测后图片", 1);
cvShowImage("检测后图片", src);
cvWaitKey(0);
cvDestroyWindow("原始图片");
cvDestroyWindow("检测后图片");
}

<think>首先,用户的问题是:使用OpenCV根据相机像素焦距比例测量A4纸中黑色形状的实际尺寸。具体来说,A4纸有一个2cm的黑边框,内部有黑色等边三角形正方形形。用户想测量这些形状的边长和直径的实际值。 关键点: - 输入:一张A4纸(210mm x 297mm),带有2cm宽的黑边框。 - 内部形状:黑色等边三角形正方形形。 - 目标:测量这些形状的实际尺寸(边长、直径)。 - 方法:利用相机像素焦距比例关系。 我需要基于相机标定和OpenCV来实现这个。参考了系统提供的引用: - 引用[1]:讨论相机标定,使用Zhang的方法(张正友标定法),涉及内参和畸变参数。 - 引用[2]:提到姿态优化和自适应体素化,但可能不直接相关,除非涉及3D重建。 用户的需求是2D测量,所以焦点在相机标定和透视变换。 步骤: 1. **相机标定**:首先需要标定相机以获取内参矩阵和畸变系数。这样可以将像素坐标转换为世界坐标。 - 内参矩阵包括焦距(fx, fy)和主点(cx, cy)。 - 使用棋盘格或类似模式进行标定。 2. **检测A4纸**:因为A4纸有黑边框,应该可以检测到整个A4纸的轮廓。A4纸的实际尺寸是已知的(210mm x 297mm),所以可以用作参考对象。 - 黑边框宽度2cm,所以内部可测量区域尺寸:宽度 = 210mm - 2*20mm = 170mm,高度 = 297mm - 2*20mm = 257mm。但用户要测量内部形状,所以边框是固定的。 3. **检测内部形状**:使用OpenCV检测三角形正方形形的轮廓。 - 对于等边三角形正方形,可以通过轮廓近似来识别。 4. **计算实际尺寸**:利用相机像素焦距比例关系。 - 原理:如果知道物体的实际大小和其在图像中的像素大小,结合相机内参,可以计算距离或尺寸。 - 但这里,相机可能不是正对A4纸,所以可能有透视失真。需要校正透视。 - 更好的方法是:使用A4纸的四个角点进行透视变换(homography),将图像中的A4纸区域映射到实际尺寸的平面。 具体方法: - **步骤1: 相机标定** - 使用OpenCV的`cv2.calibrateCamera`函数,需要一组图像中的棋盘格角点。 - 引用[1]提到Zhang的方法,这正是OpenCV实现的。 - 标定后得到内参矩阵(camera matrix)和畸变系数(distortion coefficients)。 - **步骤2: 图像预处理** - 加载图像。 - 应用畸变校正(undistort)使用标定参数。 - 检测A4纸:由于有黑边框,可以阈值化图像(例如,二值化),找到外部轮廓。A4纸应该是最大的轮廓。 - 获取A4纸的四个角点。 - **步骤3: 透视变换** - 定义A4纸的实际尺寸(210mm x 297mm)。但边框是2cm,所以内部区域尺寸:宽度 = 210 - 40 = 170mm,高度 = 297 - 40 = 257mm?不,边框是围绕的,所以内部可测量区域是(210-2*20, 297-2*20) = (170, 257)mm。 - 然而,用户想测量内部形状,所以我们需要将整个A4纸区域映射到实际尺寸。 - 在透视变换中,我们将图像中的A4纸四边形映射到一个矩形(210mm x 297mm)。 - 然后,在变换后的图像中,内部形状的像素坐标可以直接转换为实际坐标,因为变换后图像是正视图。 - **步骤4: 检测内部形状** - 在变换后的图像中,检测黑色形状。 - 阈值化以提取黑色区域(假设背景是白色A4纸)。 - 对于每个形状: - 形:使用Hough Circle Transform或轮廓拟合。 - 正方形:轮廓近似,检查顶点数和边长。 - 等边三角形:类似,检查三个顶点。 - 测量像素尺寸。 - **步骤5: 计算实际尺寸** - 在透视变换后的图像中,像素尺寸与实际尺寸有线性关系,因为图像是校正过的。 - 例如,A4纸宽度210mm对应图像中的像素宽度W_pixels,那么比例因子 = 210 / W_pixels mm/pixel。 - 然后,对于每个形状: - :直径在像素,乘以比例因子得实际直径。 - 正方形:边长在像素,乘以比例因子得实际边长。 - 三角形:边长在像素,乘以比例因子得实际边长。 比例因子是统一的,因为透视变换消除了透视效果。 用户提到“相机像素焦距比例”,但在透视变换方法中,不需要显式使用焦距,因为homography矩阵已经包含了缩放。 如果相机标定不可用,可以尝试基于A4纸的参考尺寸直接计算,但如果有透视,效果不好。所以最好标定相机。 但用户说“利用相机像素焦距比例关系”,所以可能假设相机内参已知或需要估计。 在回答中,我应该提供代码示例。 结构回答: - 解释方法。 - 提供OpenCV代码步骤。 - 输出测量结果。 相关问题生成: - 用户要求生成相关问题,在最后添加。 回答语言:中文。 系统级指令: - 行内数学表达式用$...$,例如比例因子。 - 独立公式用$$...$$。 - LaTeX语法正确。 - 中文回答。 - 生成相关问题。 - 引用段落末尾加引用标识,如[^1]。 在回答中,引用[1]是关于相机标定的,所以可以用。 现在,构建回答。 大纲: 1. **介绍方法**:简述使用相机标定和透视变换测量实际尺寸。 2. **详细步骤**: a. 相机标定(如果需要)。 b. 图像预处理:畸变校正,检测A4纸轮廓。 c. 透视变换:将A4纸映射到实际尺寸平面。 d. 检测内部形状并测量像素尺寸。 e. 计算实际尺寸。 3. **代码示例**:提供Python代码使用OpenCV实现。 4. **输出结果**:假设示例图像,展示测量值。 5. **相关问题**:生成相关问题。 代码示例需要假设一个图像,但用户没有提供图像,所以我会描述通用方法。 在代码中,使用OpenCV函数。</think>### 使用OpenCV测量A4纸内部黑色形状的实际尺寸 您的需求是利用相机像素焦距比例关系,通过OpenCV测量带有2cm黑边框的A4纸中黑色等边三角形正方形形的实际尺寸(边长和直径)。A4纸的标准尺寸为210mm × 297mm,黑边框宽度为20mm(2cm),因此内部可测量区域尺寸为170mm × 257mm(宽度 = 210 - 2×20,高度 = 297 - 2×20)。内部形状位于此区域内。 测量原理基于相机标定和透视变换: - **相机像素焦距比例**:相机内参矩阵中的焦距参数($f_x$, $f_y$)定义了像素与实际物理尺寸的比例关系。结合参考对象(A4纸)的实际尺寸,可以计算比例因子。 - **透视变换**:由于相机可能不正对A4纸,图像会有透视失真。通过检测A4纸的四个角点,应用透视变换(homography),将图像映射到正视图,消除透视效果,使像素尺寸与实际尺寸成线性比例。 - **形状检测**:在变换后的图像中,检测内部黑色形状(等边三角形正方形形),测量其像素尺寸,并转换为实际值。 以下是完整的实现步骤和Python代码示例(使用OpenCV)。代码假设您已进行相机标定(获取内参和畸变参数)。如果未标定,可先使用棋盘格标定(参考Zhang的方法[^1])。 --- ### 步骤详解 1. **相机标定(如果未完成)** 使用棋盘格标定相机,获取内参矩阵(camera matrix)和畸变系数(distortion coefficients)。内参矩阵形式为: $$ K = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} $$ 其中 $f_x$, $f_y$ 是焦距(像素单位),$c_x$, $c_y$ 是主点坐标。标定过程可参考OpenCV的`cv2.calibrateCamera`函数[^1]。 2. **图像预处理和畸变校正** - 加载图像,应用畸变校正(使用标定参数)。 - 二值化图像:由于A4纸有黑边框,用阈值分割提取黑色区域。 - 检测A4纸轮廓:找到最大轮廓(对应A4纸外部),并获取其四个角点。 3. **透视变换** - 定义A4纸实际尺寸(210mm × 297mm)。将图像中的A4纸四边形映射到正视图矩形。 - 计算homography矩阵,并应用透视变换。变换后图像中,像素尺寸与实际尺寸的比例因子统一: $$ \text{比例因子} = \frac{\text{实际宽度}}{\text{像素宽度}} \quad \text{或} \quad \frac{\text{实际高度}}{\text{像素高度}} $$ 例如,A4纸宽度210mm对应像素宽度$W_p$,则比例因子 $s = \frac{210}{W_p}$ mm/pixel。 4. **检测内部黑色形状** - 在变换后的图像中,二值化提取黑色形状(背景为白色A4纸)。 - 分别检测形状: - **形**:使用Hough变换或轮廓拟合,测量直径(像素)。 - **正方形**:轮廓近似,筛选四边形并检查边长相等,测量边长(像素)。 - **等边三角形**:轮廓近似,筛选三角形并检查边长相等,测量边长(像素)。 5. **计算实际尺寸** - 使用比例因子 $s$ 将像素尺寸转换为实际尺寸(mm): - 直径实际值 = 像素直径 × $s$ - 正方形边长实际值 = 像素边长 × $s$ - 三角形边长实际值 = 像素边长 × $s$ --- ### Python代码实现 以下是完整代码示例(使用OpenCV)。假设图像文件名为`a4_image.jpg`,相机标定参数已知(内参矩阵`camera_matrix`和畸变系数`dist_coeffs`)。如果未标定,可跳过畸变校正步骤,但精度可能降低。 ```python import cv2 import numpy as np # 加载图像 image = cv2.imread('a4_image.jpg') if image is None: raise FileNotFoundError("图像文件未找到") # 相机标定参数(示例值,需替换为您的实际参数) camera_matrix = np.array([[1000, 0, 640], [0, 1000, 360], [0, 0, 1]], dtype=np.float32) # 内参矩阵 dist_coeffs = np.array([-0.1, 0.01, 0, 0], dtype=np.float32) # 畸变系数 # 步骤1: 畸变校正 undistorted = cv2.undistort(image, camera_matrix, dist_coeffs) # 步骤2: 预处理和检测A4纸轮廓 gray = cv2.cvtColor(undistorted, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, 50, 255, cv2.THRESH_BINARY_INV) # 阈值化提取黑色区域(边框和形状) contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) largest_contour = max(contours, key=cv2.contourArea) # A4纸是最大轮廓 # 近似多边形,获取四个角点 epsilon = 0.02 * cv2.arcLength(largest_contour, True) approx = cv2.approxPolyDP(largest_contour, epsilon, True) if len(approx) != 4: raise ValueError("未检测到A4纸四边形轮廓") # 排序角点:顺序为 [左上, 右上, 右下, 左下] pts_src = np.array([point[0] for point in approx], dtype=np.float32) pts_src = pts_src[np.argsort(pts_src[:, 1])] # 按y排序 top = pts_src[:2][np.argsort(pts_src[:2, 0])] bottom = pts_src[2:][np.argsort(pts_src[2:, 0])] pts_src = np.vstack((top, bottom)) # 步骤3: 透视变换 a4_width_mm, a4_height_mm = 210, 297 # A4纸实际尺寸 pts_dst = np.array([[0, 0], [a4_width_mm, 0], [a4_width_mm, a4_height_mm], [0, a4_height_mm]], dtype=np.float32) H, _ = cv2.findHomography(pts_src, pts_dst) # 计算homography矩阵 warped = cv2.warpPerspective(undistorted, H, (a4_width_mm, a4_height_mm)) # 变换到正视图 # 计算比例因子 (mm/pixel) s = a4_width_mm / warped.shape[1] # 或使用高度 # 步骤4: 检测内部黑色形状(在变换后图像中) warped_gray = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY) _, warped_binary = cv2.threshold(warped_gray, 50, 255, cv2.THRESH_BINARY_INV) # 提取黑色形状 inner_contours, _ = cv2.findContours(warped_binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 存储结果 results = {} for cnt in inner_contours: area = cv2.contourArea(cnt) if area < 100: # 忽略小噪点 continue # 近似形状 peri = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.04 * peri, True) num_vertices = len(approx) # 检测形 if num_vertices > 8: # 轮廓点较多 (x, y), radius = cv2.minEnclosingCircle(cnt) diameter_pixel = 2 * radius results['circle_diameter_mm'] = diameter_pixel * s cv2.circle(warped, (int(x), int(y)), int(radius), (0, 255, 0), 2) # 检测正方形(4个顶点) elif num_vertices == 4: # 检查边长是否相等(近似正方形) side_lengths = [np.linalg.norm(approx[i] - approx[(i+1)%4]) for i in range(4)] if max(side_lengths) - min(side_lengths) < 10: # 边长差异阈值 side_pixel = np.mean(side_lengths) results['square_side_mm'] = side_pixel * s cv2.drawContours(warped, [approx], -1, (0, 255, 0), 2) # 检测等边三角形(3个顶点) elif num_vertices == 3: # 检查边长是否相等 side_lengths = [np.linalg.norm(approx[i] - approx[(i+1)%3]) for i in range(3)] if max(side_lengths) - min(side_lengths) < 10: side_pixel = np.mean(side_lengths) results['triangle_side_mm'] = side_pixel * s cv2.drawContours(warped, [approx], -1, (0, 255, 0), 2) # 输出结果 print("测量结果 (mm):") for shape, size in results.items(): print(f"{shape}: {size:.2f} mm") # 保存结果图像(可选) cv2.imwrite('result.jpg', warped) ``` ### 代码说明和注意事项 - **输入要求**:图像需清晰显示A4纸,黑边框和内部形状对比度高。内部形状应为纯黑色,背景白色。 - **参数调整**: - 阈值化参数(如`cv2.threshold`中的50)需根据图像亮度调整。 - 如果相机未标定,跳过畸变校正步骤(删除`cv2.undistort`),但测量精度可能降低。 - **输出**:代码输出每个形状的实际尺寸(mm),并在图像上标记检测到的形状(保存为`result.jpg`)。 - **精度**:依赖于相机标定质量和A4纸轮廓检测。实际误差通常在1-5%内,如果相机正对A4纸,精度更高。 ### 示例结果 假设图像中A4纸变换后宽度为1000像素,比例因子 $s = 210 / 1000 = 0.21$ mm/pixel: - 直径像素为200,则实际直径 = 200 × 0.21 = 42.0 mm - 正方形边长像素为150,则实际边长 = 150 × 0.21 = 31.5 mm - 三角形边长像素为180,则实际边长 = 180 × 0.21 = 37.8 mm 此方法利用相机焦距比例(内参)和透视变换,确保尺寸计算准确[^1]。如果图像有严重畸变,必须先标定相机以校正。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值