#python #opencv
#python #opencv
Вопрос:
для моего домашнего проекта я хочу создать какой-то ambilight. Подсветка уже работает, когда я передаю фотографию в программу на Python и помещаю фотографию в сетку 78×42, а затем отправляю каждый цвет края на светодиодный индикатор. Светодиоды расположены за телевизором для создания фонового свечения.
На данный момент эффект уже очень хорош.
Теперь следующий шаг:
Raspberry-Pi с подключенной камерой RPI должен быть направлен на телевизор, а цвета границ должны быть определены и отправлены на светодиоды.
Чтобы программа узнала, где находится телевизор, я уже написал программу, которая сначала создает 4 изображения (все белые, все красные, все зеленые и все синие), отправляет эти изображения в Chromecast и отображает их. Из каждого изображения создается фотография. Итак, у меня есть 4 фотографии, на которых можно увидеть телевизор. Каждый раз телевизор показывает только один цвет.
Я сделал снимки в красном, зеленом и синем цветах, чтобы позже выполнить цветокоррекцию или откалибровать цвета. Но это не то, о чем я хочу здесь поговорить.
Сначала я хочу распознать телевизор. У меня нет опыта работы с OpenCV. У меня уже есть следующий скрипт, но я не могу продвинуться дальше.
import skimage
import skimage.feature
import skimage.viewer
import matplotlib
import cv2
image = skimage.io.imread(fname='tvImageW.jpg', as_gray=True)
edges = skimage.feature.canny(
image=image,
sigma=1,
low_threshold=0.1,
high_threshold=1.2,
)
matplotlib.image.imsave('edges.png', edges)
Исходное изображение телевизора с простым белым содержимым
Результат выполнения скрипта python
У кого-нибудь есть идея или план, как я могу действовать?
Я убежден, что это было бы относительно легко реализовать с помощью OpenCV, если бы вы знали свой путь…
СПАСИБО за каждую подсказку.
С помощью Марио я сейчас нахожусь в точке поиска контура. Я также обнаружил, как определить 4 угла телевизора. Вот мой код на данный момент:
import cv2
from math import sqrt
im = cv2.imread('tvImageW.jpg')
edges = cv2.Canny(im, 100, 200)
contours, hierarchy = cv2.findContours(edges,
cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# find the biggest countour (c) by the area
c = max(contours, key = cv2.contourArea)
# find the perimeter of the contour
perim = cv2.arcLength(c, True)
# setting the precision
epsilon = 0.02*perim
# approximating the contour with a polygon
approxCorners = cv2.approxPolyDP(c, epsilon, True)
cv2.drawContours(im, [c], 0, (255,255,0), 2)
cv2.drawContours(im, approxCorners, -1, (0, 0, 255), 3)
cv2.imwrite('result.png', im)
Теперь у меня есть 3 идеи, которые я буду тестировать дальше:
- разделите линии между точками на 78 (или 42) частей. Затем изучите отдельные сетки на предмет среднего значения цвета.
- используйте 4 угловые точки и следующее руководство, чтобы придать изображению правильную перспективу, а затем используйте мою (уже существующую) процедуру: https://www.pyimagesearch.com/2014/08/25/4-point-opencv-getperspective-transform-example /
- создайте новое тестовое изображение и там отметьте интересующие меня области и снова распознайте эти контуры. Каждый обнаруженный контур в контуре телевизора будет затем светодиодным пикселем в конце.
Я подозреваю, что точка 2 слишком медленная во время выполнения. Поэтому я начну с первого пункта. Здесь, однако, могут возникнуть проблемы с перспективой. Если проблемы приемлемы, у меня есть результат. Если это неприемлемо, я попробую пункт 3….
Вот попытка представить проблему графически. Ниже трапеции телевизора (тв-контур). Над исправленным изображением телевизора с интересующими меня областями. 
Комментарии:
1. я опубликовал код для распознавания телевизора. Теперь у нас есть точки границ телевизора. Какой следующий шаг?
2. Почему бы не использовать средний цвет всего экрана без полей / ячеек 78 или 42 частей и без аффинных преобразований деформации? Сколько светодиодов у вас есть?
3. Имеется 240 светодиодов: верхний и нижний 78, правый и левый 42
4. Я включил попытку графически показать проблему выше. Теперь заметил, что трапеция расположена неправильно. Но проблема все равно должна быть ясна.
5. Размер телевизора составляет 130 см x 70 см
Ответ №1:
Кажется, что tv — это максимальный контур области. В темноте вам нужны разные параметры
создайте папку и назовите ее «pyimagesearch». За этой папкой мы поместим пустой __init__.py
файл и transform.py
файл, содержащий функции для деформации изображения.
Это наш основной файл:
import cv2
import numpy as np
from pyimagesearch.transform import *
im=cv2.imread('images/tv-wall.jpg')
img = im.copy()
edges = cv2.dilate(cv2.Canny(im,1,100),(2,2) ,iterations=1)
contours, hierarchy = cv2.findContours(edges,
cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# find the biggest countour (c) by the area
c = max(contours, key = cv2.contourArea)
cv2.drawContours(img, [c], 0, (255,255,0), 2)
# find the perimeter of the contour
perim = cv2.arcLength(c, True)
# setting the precision
epsilon = 0.02*perim
# approximating the contour with a polygon
approxCorners = cv2.approxPolyDP(c, epsilon, True)
#draw 4 yellow corners points
for p in approxCorners:
cv2.circle(img,(p[0],p[1]),3,(0,255,255),3)
cv2.imshow('img',img)
Теперь мы используем ‘approxCorners’ для искажения изображения ‘im’
с исправленными функциями из https://www.pyimagesearch.com/2014/08/25/4-point-opencv-getperspective-transform-example /
pts = np.array( approxCorners , dtype = "float32")
warped = four_point_transform(im, pts)
#Your tv w/h ratio
ratio=130/70
wrp_h, wrp_w ,depth = warped.shape
rsz_h = int(wrp_h * ratio)
#warped image stretch resize and show
rwarped = cv2.resize(warped,(wrp_w,rsz_h))
cv2.imshow("Warped",rwarped)
cv2.waitKey(0)
Структура папок:
pyimagesearch/__init__.py
pyimagesearch/transform.py
Это ‘transform.py ‘ файл за папкой ‘pyimagesearch’
import numpy as np
import cv2
#This function is different from the original at pyimagesearch
def order_points(pts):
spts=sorted(pts,key=lambda k:[k[1],k[0]])
return np.array( [
tuple(spts[0]),#0 ok
tuple(spts[1]),#1 ok
tuple(spts[3]),#3 ok
tuple(spts[2]) #2 ok
])
def four_point_transform(image, pts):
# obtain a consistent order of the points and unpack them
# individually
rect = order_points(pts)
print("ptsorderd rect",rect)
print("rect type", type(rect))
print("rect shape", rect.shape)
(tl, tr, br, bl) = rect
# compute the width of the new image, which will be the
# maximum distance between bottom-right and bottom-left
# x-coordiates or the top-right and top-left x-coordinates
widthA = np.sqrt(((br[0] - bl[0]) ** 2) ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) ((tr[1] - tl[1]) ** 2))
maxWidth = max(int(widthA), int(widthB))
# compute the height of the new image, which will be the
# maximum distance between the top-right and bottom-right
# y-coordinates or the top-left and bottom-left y-coordinates
heightA = np.sqrt(((tr[0] - br[0]) ** 2) ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA), int(heightB))
# now that we have the dimensions of the new image, construct
# the set of destination points to obtain a "birds eye view",
# (i.e. top-down view) of the image, again specifying points
# in the top-left, top-right, bottom-right, and bottom-left
# order
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]], dtype = "float32")
# compute the perspective transform matrix and then apply it
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
# return the warped image
return warped
Комментарии:
1. Спасибо. Итак, теперь я нашел фрейм. Как мне поступить дальше. Мне пришлось бы разделить контур на поля 78 на 42, а затем изучить каждое из полей по краям, чтобы определить средний цвет….
2. То, что вам нужно, можно сделать несколькими способами. Для большей наглядности вы можете добавить изображение для рисования / рисования желаемого результата?
3. Большое спасибо. Я обновил свой оригинальный пост.
Ответ №2:
Для полноты картины я хотел бы опубликовать свой окончательный результат здесь. В конце скрипт создает JSON, в котором сохраняется область пикселей для каждого отдельного светодиода. Затем эта область пикселей используется основной программой.
В целом все работает очень гладко. На удивление даже хорошо.
В основной программе более высокая скорость может привести к (даже) лучшему взаимодействию, но она уже работает лучше, чем я ожидал.
Если есть желание, я выложу полный исходный код на Github:
- Скрипт, который отправляет изображения на телевизор через Chromecast.
- Скрипт, который обнаруживает телевизор и сохраняет результат интересных пикселей в файл JSON для основной программы.
- Основной скрипт, который использует камеру RPI, определяет цвета и отправляет их в NodeMCU.
- Код Arduino NodeMCU, с помощью которого цвета передаются на привязанный 4-метровый WS2812B за телевизором.
detectTv.py
import constant
import detectTvHelper as helper
import cv2
import numpy as np
import json
im = cv2.imread('tvImageW.jpg')
edges = cv2.dilate(cv2.Canny(im,1,100),(2,2) ,iterations=1) #cv2.Canny(im, 100, 200)
contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# find the biggest countour (c) by the area
c = max(contours, key = cv2.contourArea)
# find the perimeter of the contour
perim = cv2.arcLength(c, True)
# setting the precision
epsilon = 0.02*perim
# approximating the contour with a polygon
approxCorners = cv2.approxPolyDP(c, epsilon, True)
cv2.drawContours(im, [c], 0, (255,255,0), 2)
cv2.drawContours(im, approxCorners, -1, (0, 0, 255), 3)
cv2.imwrite('result.png', im)
orderedPoints = helper.order_points(approxCorners)
points = helper.getPointsBetweenPoints(orderedPoints[0][0], orderedPoints[0][1], orderedPoints[1][0], orderedPoints[1][1], 78)
points = helper.getPointsBetweenPoints(orderedPoints[1][0], orderedPoints[1][1], orderedPoints[2][0], orderedPoints[2][1], 42)
points = helper.getPointsBetweenPoints(orderedPoints[2][0], orderedPoints[2][1], orderedPoints[3][0], orderedPoints[3][1], 78)
points = helper.getPointsBetweenPoints(orderedPoints[3][0], orderedPoints[3][1], orderedPoints[0][0], orderedPoints[0][1], 42)
result={}
cimg = np.zeros_like(im)
cv2.fillPoly(cimg, pts =[c], color=255)
cBRx,cBRy,cBRw,cBRh = cv2.boundingRect(c)
pts = helper.getMarkedCoordinates(cimg, cBRx, cBRy, cBRw, cBRh)
led = 0
for p in points:
cimg2 = np.zeros_like(im)
cv2.circle(cimg2, (p[0], p[1]), radius=constant.COLOR_RADIUS, color=255, thickness=-1)
pts2 = helper.getMarkedCoordinates(cimg2, p[0]-5, p[1]-5, 10, 10)
inters = helper.intersect(pts, pts2)
result[led] = inters
print(led, len(inters))
led = 1
with open('pixels.json', 'w') as f:
json.dump(result, f)
detectTvHelper.py
#order corners tl, tr, bl, br
def order_points(approxCorners):
pts = [
[approxCorners[0][0][0], approxCorners[0][0][1]],
[approxCorners[1][0][0], approxCorners[1][0][1]],
[approxCorners[2][0][0], approxCorners[2][0][1]],
[approxCorners[3][0][0], approxCorners[3][0][1]]]
rect = [[0, 0]] * 4
pts.sort()
if (pts[0][1] < pts[1][1]):
rect[0] = pts[0]
rect[3] = pts[1]
else:
rect[0] = pts[1]
rect[3] = pts[0]
if (pts[2][1] < pts[3][1]):
rect[1] = pts[2]
rect[2] = pts[3]
else:
rect[1] = pts[3]
rect[2] = pts[2]
# return the ordered coordinates
return rect
def getPointsBetweenPoints(x1, y1, x2, y2, points):
res=[]
for i in range(points):
newX = x1 ((x2 - x1) / (points) * (i 0.5))
newY = y1 ((y2 - y1) / (points) * (i 0.5))
res.append([round(newX), round(newY)])
return res
def getMarkedCoordinates(image, x, y, w, h):
res = []
for _x in range(x, x w):
for _y in range(y, y h):
pixel = image[_y][_x]
if (pixel.max() > 0):
res.append([_x, _y])
return res
def intersect(pixels1, pixels2):
res = []
m = {}
if len(pixels1)<len(pixels2):
pixels1,pixels2 = pixels2,pixels1
for i in pixels1:
m[i[0]*1000 i[1]] = True
for i in pixels2:
if i[0]*1000 i[1] in m:
res.append(i)
return res
Поскольку у меня есть только поверхностные знания Python, я могу представить, что есть возможности для улучшения в том или ином месте. Я открыт для подсказок.
Однако производительность здесь играет не самую большую роль. В основной программе важен каждый бит производительности.