まとめ
この実験では、既知のコインの直径に基づいて、絵の中の本の長さと幅、および本の右上隅に鉛筆で描かれた円の外径を予測する必要があります。まず画像を修正し、コインの輪郭を見つけ、コインの直径が占めるピクセルサイズを計算します。次に、実際のサイズとピクセルの比例係数を取得し、本によって描かれた円の輪郭を見つけます。と鉛筆を描き、その輪郭が占めるピクセルサイズと比例係数を計算して、2つの実際のサイズを推定します。予測される本の長さは 20.150000 cm、本の幅は 15.250000 cm、本の右上に鉛筆で描かれた円の外径は 4.100000 cm です。
1 はじめに
問題の再説明:本の右下隅に 1 元 (直径 2.5 cm) が配置されていることがわかりました。コンピュータ ビジョン テクノロジーを使用して、実際の金額を予測してください。写真のターゲットのサイズ。
1. 絵の中の本の長さと幅を予想します(単位:センチメートル)。
2 本の右上隅に鉛筆で描いた円の外径(単位:センチメートル)を予想します。
2、解决方法
この実験では、まず既存の関連ソリューションに基づいて、既存のコードを参照して修正を加えました。
このうち、最初に変更する部分は、Canny オペレーターのエッジ検出のしきい値です。
imgCanny = cv2.Canny(imgBlur, 35, 70)
上記の結果が最良であると思います。
変形例2は、形状検出機能部の定義である。テスト中に、本の輪郭の端、手で描いた円、コインがすべて円として扱われることがわかりました(本の輪郭の端が完全に閉じていないためかもしれません)。そこで、これらの輪郭を格納する円リストを定義しました。等高線マップから、本、手で描かれた円、コインがたまたま最大の等高線であることがわかります。
したがって、最初に本の輪郭が占めるピクセル サイズを取得してから、サークルから本の輪郭を削除できます。類推して、鉛筆で描かれた円とコインの輪郭がそれぞれ占めるピクセル サイズを取得します。最後に、コインの実際のサイズ 2.5 センチメートルを代入して実際のサイズとピクセルの比例係数を取得し、それに本と鉛筆で描いた円の輪郭が占めるピクセル サイズをそれぞれ掛けます。本と鉛筆で描かれた円の推定実際のサイズを取得します。操作中、撮影角度、反射、影などの問題により境界線の描画に誤差が生じ、コインや鉛筆で描いた円の境界ボックスが正方形にならず、推定誤差が発生します。鉛筆で描かれた円のスケール係数と推定される実際のサイズを計算するために、直径の代わりに長さと幅の平均を取りました。
# 定义查找书本、手绘圆圈、硬币轮廓并绘制边界框
def draw_Shape(image, counters):
max_area = 0
shape_contour = None
for contour in counters:
area = cv2.contourArea(contour)
if max_area < area:
max_area = area
shape_contour = contour
cv2.drawContours(image, shape_contour, -1, (255, 0, 0), 4) # 绘制轮廓线
perimeter = cv2.arcLength(shape_contour, True) # 计算轮廓周长
approx = cv2.approxPolyDP(shape_contour, 0.02 * perimeter, True) # 获取轮廓角点坐标
CornerNum = len(approx) # 轮廓角点的数量
x, y, w, h = cv2.boundingRect(approx) # 获取坐标值和宽度、高度
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2) # 绘制边界框
return shape_contour, w, h
# 定义轮廓集合删去最大的轮廓
def del_max_contours(contours, contour):
new_contours = []
for circle in contours:
if np.array_equal(circle, contour):
continue
else:
new_contours.append(circle)
return new_contours
# 定义形状检测函数
def ShapeDetection(img):
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 寻找轮廓点
Circle = []
for obj in contours:
area = cv2.contourArea(obj) # 计算轮廓内区域的面积
cv2.drawContours(imgContour, obj, -1, (255, 0, 0), 4) # 绘制轮廓线
perimeter = cv2.arcLength(obj, True) # 计算轮廓周长
approx = cv2.approxPolyDP(obj, 0.02 * perimeter, True) # 获取轮廓角点坐标
CornerNum = len(approx) # 轮廓角点的数量
x, y, w, h = cv2.boundingRect(approx) # 获取坐标值和宽度、高度
if CornerNum > 4:
Circle.append(obj)
最終結果:
ただし、画像は補正されていないため、予測値には多少の誤差が生じます。そこで、ネットで補正方法を調べてから計測してみました。繰り返しのテストにより、次のパラメータを使用して輪郭を完全に描画します。
# 高斯模糊
imgBlur = cv2.GaussianBlur(imgGray, (9, 9), 0)
# Canny算子边缘检测
imgCanny = cv2.Canny(imgBlur, 20, 52)
kernel = np.ones((5, 5))
imgDial = cv2.dilate(imgCanny, kernel, iterations=6) # 膨胀
imgThre = cv2.erode(imgDial, kernel, iterations=5) # 腐蚀
次に、本のエッジ点を通じて本が修正され、修正プロセス中に本の輪郭のサイズ (ピクセル) が取得されます。
# 将轮廓拐点重新排列的方法
def reorder(myPoints):
myPointsNew = np.zeros_like(myPoints)
myPoints = myPoints.reshape((4, 2))
add = myPoints.sum(1)
myPointsNew[0] = myPoints[np.argmin(add)]
myPointsNew[3] = myPoints[np.argmax(add)]
diff = np.diff(myPoints, axis=1)
myPointsNew[1] = myPoints[np.argmin(diff)]
myPointsNew[2] = myPoints[np.argmax(diff)]
return myPointsNew
# 图像矫正的方法
def warpImg(img, points, w, h, pad=6):
points = reorder(points)
pts1 = np.float32(points)
pts2 = np.float32([[0, 0], [w, 0], [0, h], [w, h]])
matrix = cv2.getPerspectiveTransform(pts1, pts2)
imgWrap = cv2.warpPerspective(img, matrix, (w, h))
imgWrap = imgWrap[pad:imgWrap.shape[0] - pad, pad:imgWrap.shape[1] - pad]
return imgWrap
# 定义形状检测函数
def ShapeDetection(img):
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 寻找轮廓点
Rectangle = []
for obj in contours:
area = cv2.contourArea(obj) # 计算轮廓内区域的面积
cv2.drawContours(imgContour, obj, -1, (255, 0, 0), 4) # 绘制轮廓线
perimeter = cv2.arcLength(obj, True) # 计算轮廓周长
approx = cv2.approxPolyDP(obj, 0.02 * perimeter, True) # 获取轮廓角点坐标
CornerNum = len(approx) # 轮廓角点的数量
x, y, w, h = cv2.boundingRect(approx) # 获取坐标值和宽度、高度
if CornerNum == 4:
Rectangle.append([obj, area, approx, h, w])
Rectangle = sorted(Rectangle, key=lambda x: x[1], reverse=True)
book_approx = Rectangle[0][2]
w = Rectangle[0][4]
h = Rectangle[0][3]
img_warp = warpImg(imgContour, book_approx, w, h)
return img_warp, h, w
修正後の写真:
修正後の課題は、コインの輪郭と大圏の輪郭を見つけることです。このステップでは、最初に形状認識手法を使用して輪郭点が 4 つ以上の形状を見つけましたが、描画後の結果は理想的ではありませんでした。円の形状が楕円に似ていたため、ハフ変換を使用しました。ハフ円検出。
# 高斯模糊
imgBlur = cv2.GaussianBlur(imgGray, (7, 7), 0)
# 霍夫变换
circles = cv2.HoughCircles(imgBlur, cv2.HOUGH_GRADIENT, 1, 50,
param1=20, param2=54, minRadius=10, maxRadius=1000)
print(circles[0])
if circles is not None:
circles = np.round(circles[0, :]).astype("int")
for (x, y, r) in circles:
cv2.circle(imgWarpContour, (x, y), r, (0, 255, 0), 2)
結果をプロットします。
最後に、ハフ変換により検出されたコインの輪郭の直径と実際の直径を比較して比例係数を求め、予測される本の縦横と鉛筆で描いた円の直径を求めます。
3. 実験結果
この実験の結果は期待どおりだと思います。
画像補正前は、画像の反射や影、傾きなどの干渉により、描いた輪郭線に大きな誤差が生じます。画像が補正された後、傾斜角や反射などの低減などのいくつかの干渉要因が除去され、得られた輪郭の誤差が減少します。
しかし、補正後の画像には、鉛筆で描いた円やコインの境界線などにゴーストが発生し、描画された輪郭に誤差が生じるなど、干渉要素が依然として存在します。
4 結論
この実験では、本、コイン、鉛筆で描かれた円の輪郭をどのように抽出するかが主な問題であり、補正前の画像では誤差が大きくなりますが、補正後の画像では輪郭誤差が大幅に減少しています。 。 OpenCVに基づく検出手法を提案し、Pythonコードによる具体的な実装プロセスをデモしました。この方法は、画像内のグラフィック オブジェクトの位置と半径を検出し、オブジェクトのサイズを予測するために使用できます。
まず、本に対してガウス フィルタリングが実行され、Canny オペレータがエッジを検出します。次に、膨張接続と腐食接続を使用してオブジェクトの小さな穴を埋め、切断された輪郭を接続します。次に、本の 4 つのエッジ ポイントを取得し、これらのエッジ ポイントに基づいて透視変換を実行して、画像補正効果を得ることができます。次に、ハフ変換を使用して円検出を実行し、コインと大きな円の半径を取得し、最終的に結果を取得します。
干渉要因はほとんど排除されているので、予想通りの予測結果になっていると感じます。
完全なコード
import cv2
import numpy as np
# 将轮廓拐点重新排列的方法
def reorder(myPoints):
myPointsNew = np.zeros_like(myPoints)
myPoints = myPoints.reshape((4, 2))
add = myPoints.sum(1)
myPointsNew[0] = myPoints[np.argmin(add)]
myPointsNew[3] = myPoints[np.argmax(add)]
diff = np.diff(myPoints, axis=1)
myPointsNew[1] = myPoints[np.argmin(diff)]
myPointsNew[2] = myPoints[np.argmax(diff)]
return myPointsNew
# 图像矫正的方法
def warpImg(img, points, w, h, pad=6):
points = reorder(points)
pts1 = np.float32(points)
pts2 = np.float32([[0, 0], [w, 0], [0, h], [w, h]])
matrix = cv2.getPerspectiveTransform(pts1, pts2)
imgWrap = cv2.warpPerspective(img, matrix, (w, h))
imgWrap = imgWrap[pad:imgWrap.shape[0] - pad, pad:imgWrap.shape[1] - pad]
return imgWrap
# 定义形状检测函数--书本
def ShapeDetection(img):
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 寻找轮廓点
Rectangle = []
for obj in contours:
area = cv2.contourArea(obj) # 计算轮廓内区域的面积
cv2.drawContours(imgContour, obj, -1, (255, 0, 0), 4) # 绘制轮廓线
perimeter = cv2.arcLength(obj, True) # 计算轮廓周长
approx = cv2.approxPolyDP(obj, 0.02 * perimeter, True) # 获取轮廓角点坐标
CornerNum = len(approx) # 轮廓角点的数量
x, y, w, h = cv2.boundingRect(approx) # 获取坐标值和宽度、高度
if CornerNum == 4:
Rectangle.append([obj, area, approx, h, w])
Rectangle = sorted(Rectangle, key=lambda x: x[1], reverse=True)
book_approx = Rectangle[0][2]
w = Rectangle[0][4]
h = Rectangle[0][3]
img_warp = warpImg(imgContour, book_approx, w, h)
return img_warp, h, w
# 定义形状检测函数--圆
def new_ShapeDetection(img):
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 寻找轮廓点
Rectangle = []
circle = []
for obj in contours:
area = cv2.contourArea(obj) # 计算轮廓内区域的面积
cv2.drawContours(imgContour, obj, -1, (255, 0, 0), 4) # 绘制轮廓线
perimeter = cv2.arcLength(obj, True) # 计算轮廓周长
approx = cv2.approxPolyDP(obj, 0.02 * perimeter, True) # 获取轮廓角点坐标
CornerNum = len(approx) # 轮廓角点的数量
x, y, w, h = cv2.boundingRect(approx) # 获取坐标值和宽度、高度
if CornerNum == 4:
Rectangle.append([obj, area, approx, x, y, w, h])
elif CornerNum > 4:
circle.append([obj, area, approx, x, y, w, h])
circle = sorted(circle, key=lambda x: x[1], reverse=True)
# 绘制大圆
cv2.drawContours(imgWarpContour, circle[1][0], -1, (255, 0, 0), 4)
# 绘制小圆
cv2.drawContours(imgWarpContour, circle[2][0], -1, (255, 0, 0), 4)
cv2.imshow("IMG", imgWarpContour)
return circle
path = 'D:\\fzu\\task.jpg'
img = cv2.imread(path)
# 调整图像大小,显示全部
img = cv2.resize(img, (0, 0), fx=0.3, fy=0.3)
imgContour = img.copy()
# 转灰度图
imgGray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
# 高斯模糊
imgBlur = cv2.GaussianBlur(imgGray, (9, 9), 0)
# Canny算子边缘检测
imgCanny = cv2.Canny(imgBlur, 20, 52)
kernel = np.ones((5, 5))
imgDial = cv2.dilate(imgCanny, kernel, iterations=6) # 膨胀
imgThre = cv2.erode(imgDial, kernel, iterations=5) # 腐蚀
cv2.imshow("imgThre", imgThre)
imgWarp, book_h, book_w = ShapeDetection(imgThre) # 形状检测
cv2.imshow("ImgWarp", imgWarp)
imgWarpContour = imgWarp.copy()
imgGray = cv2.cvtColor(imgWarp, cv2.COLOR_RGB2GRAY)
# 高斯模糊
imgBlur = cv2.GaussianBlur(imgGray, (7, 7), 0)
circles = cv2.HoughCircles(imgBlur, cv2.HOUGH_GRADIENT, 1, 50,
param1=20, param2=54, minRadius=10, maxRadius=1000)
# print(circles[0])
if circles is not None:
circles = np.round(circles[0, :]).astype("int")
for (x, y, r) in circles:
cv2.circle(imgWarpContour, (x, y), r, (0, 255, 0), 2)
cv2.imshow("IMG", imgWarpContour)
coin_diameter_px = circles[1][2] * 2
coin_size_cm = 2.5 # 假设硬币直径为25毫米
scale_factor = coin_size_cm / coin_diameter_px
draw_circle_diameter_px = circles[0][2] * 2
draw_circle_diameter_cm = draw_circle_diameter_px * scale_factor
book_w_cm = book_w * scale_factor
book_h_cm = book_h * scale_factor
print("书本的长为:%f" % book_h_cm + "厘米,书本的宽为:%f" % book_w_cm + "厘米")
print("书本右上方用铅笔画的圆圈的外圆直径为:%f" % draw_circle_diameter_cm + "厘米")
cv2.waitKey(0)