วิธีการตรวจสอบต้นคริสต์มาส [ปิด]


382

เทคนิคการประมวลผลภาพใดที่สามารถใช้ในการใช้แอพพลิเคชั่นที่ตรวจจับต้นคริสต์มาสที่แสดงในภาพต่อไปนี้

ฉันกำลังค้นหาวิธีแก้ปัญหาที่จะทำงานกับภาพเหล่านี้ ดังนั้นวิธีการที่ต้องใช้ตัวแยกประเภทการฝึกอบรมหรือการจับคู่แม่แบบไม่น่าสนใจมาก

ฉันกำลังมองหาบางอย่างที่สามารถเขียนได้ในภาษาการเขียนโปรแกรมใด ๆตราบใดที่มันใช้เทคโนโลยีโอเพนซอร์สเท่านั้น การแก้ปัญหาจะต้องทดสอบกับภาพที่ใช้ร่วมกันในคำถามนี้ มีภาพอินพุต 6 รูปและคำตอบควรแสดงผลลัพธ์ของการประมวลผลแต่ละภาพ ในที่สุดสำหรับแต่ละภาพออกจะต้องมีเส้นสีแดงลากเพื่อล้อมต้นไม้ที่ตรวจพบ

คุณจะไปเกี่ยวกับการตรวจจับต้นไม้ในรูปแบบโดยทางโปรแกรมอย่างไร


3
เราได้รับอนุญาตให้ใช้ภาพบางส่วนในการฝึกอบรมหรือควรใช้รูปภาพที่ให้มาทั้งหมดเพื่อการตรวจสอบหรือไม่? ไม่ว่าจะด้วยวิธีใดการแข่งขันที่ยอดเยี่ยม: D
Hannes Ovrén

7
@karlphillip คุณต้องการให้เราใช้ภาพเหล่านี้สำหรับการทดสอบและภาพอื่น ๆ สำหรับการฝึกอบรมหรือไม่? แค่ว่ามันไม่ชัดเจนว่าชุดฝึกอบรมคืออะไร
GilLevi

16
@karlphillip: คำแนะนำของฉัน: วางข้อกำหนด "โอเพ่นซอร์ส" มันไม่สำคัญว่าคุณใช้ภาษา / กรอบงานใด อัลกอริธึมการประมวลผลภาพ / คอมพิวเตอร์วิสัยทัศน์เป็นผู้ไม่เชื่อเรื่องภาษาดังนั้นถ้าคุณสามารถเขียนมันใน MATLAB คุณสามารถทำ OpenCV หรือกรอบงานอื่น ๆ ที่คุณต้องการได้ ... นอกจากนี้ฉันยังไม่ชัดเจนว่าคุณพิจารณาการฝึกอบรม / ทดสอบรูปอย่างไร !
Amro

2
@karlphillip ขอบคุณมากสำหรับการระดมพวกเราทุกคนให้มีส่วนร่วมใน 'ภารกิจ' ของคุณ! มันเป็นโอกาสที่ดีที่จะใช้เวลาหลายชั่วโมงอย่างมีประสิทธิผล แต่ที่สำคัญที่สุดคือเพื่อดูว่ามีวิธีการที่แตกต่างกันมากมายในปัญหาเดียว ... หวังว่าคุณจะทำมันอีกครั้งในวันที่ 1 มกราคม (อาจจะเป็น ความท้าทายของซานตาคลอส? ;-))
sepdek

2
ตกลงฉันตั้งคำถามใหม่เพื่อลบองค์ประกอบการแข่งขัน ฉันคิดว่ามันควรจะอนุญาตให้มันยืนได้ด้วยตัวของมันเอง
แบรดลาร์สัน

คำตอบ:


184

ฉันมีวิธีการที่ฉันคิดว่าน่าสนใจและแตกต่างจากที่เหลือเล็กน้อย ความแตกต่างที่สำคัญในวิธีการของฉันเมื่อเทียบกับคนอื่น ๆ คือในขั้นตอนการแบ่งส่วนภาพ - ฉันใช้อัลกอริทึมการจัดกลุ่มDBSCANจาก Scikit-Python ของ Python มันเหมาะสำหรับการค้นหารูปร่างที่ค่อนข้างไม่แน่นอนซึ่งอาจไม่จำเป็นต้องมีเซนทรอยด์เดียวที่ชัดเจน

ในระดับสูงสุดวิธีการของฉันค่อนข้างง่ายและสามารถแบ่งออกเป็น 3 ขั้นตอน ครั้งแรกที่ฉันใช้เกณฑ์ (หรือจริง ๆ แล้วตรรกะ "หรือ" ของสองเกณฑ์แยกและแตกต่างกัน) เช่นเดียวกับคำตอบอื่น ๆ อีกมากมายฉันคิดว่าต้นคริสต์มาสจะเป็นหนึ่งในวัตถุที่สว่างกว่าในฉากดังนั้นเกณฑ์แรกคือการทดสอบความสว่างแบบขาวดำอย่างง่าย พิกเซลใด ๆ ที่มีค่าสูงกว่า 220 ในระดับ 0-255 (โดยที่สีดำคือ 0 และสีขาวคือ 255) จะถูกบันทึกลงในรูปภาพไบนารีขาวดำ เกณฑ์ที่สองพยายามมองหาแสงสีแดงและสีเหลืองซึ่งโดดเด่นเป็นพิเศษในต้นไม้ที่มุมซ้ายด้านบนและด้านล่างขวาของภาพหกภาพและโดดเด่นกับพื้นหลังสีฟ้าสีเขียวซึ่งเป็นที่แพร่หลายในภาพถ่ายส่วนใหญ่ ฉันแปลงอิมเมจ rgb เป็น hsv space และต้องการให้ฮิวนั้นมีค่าน้อยกว่า 0.2 ในระดับ 0.0-1.0 (ตรงข้ามกับเส้นแบ่งระหว่างสีเหลืองและสีเขียว) หรือมากกว่า 0.95 (ตรงกับแนวระหว่างสีม่วงและสีแดง) และนอกจากนี้ฉันต้องการสีที่สว่างและอิ่มตัว: ทั้งความอิ่มตัวและค่าต้องสูงกว่า 0.7 ผลลัพธ์ของกระบวนการสองขั้นตอนมีเหตุผล "หรือ" -ed ร่วมกันและเมทริกซ์ที่เกิดขึ้นของภาพไบนารีขาวดำที่แสดงด้านล่าง:

ต้นคริสต์มาสหลังจากการนวดแป้งบน HSV เช่นเดียวกับความสว่างขาวดำ

คุณสามารถเห็นได้อย่างชัดเจนว่าแต่ละภาพมีกลุ่มพิกเซลขนาดใหญ่หนึ่งกลุ่มโดยประมาณซึ่งสอดคล้องกับที่ตั้งของต้นไม้แต่ละต้นบวกกับภาพบางส่วนที่มีกลุ่มเล็ก ๆ อีกกลุ่มหนึ่งที่สอดคล้องกับแสงในหน้าต่างของอาคารบางแห่งหรือ ฉากหลังบนขอบฟ้า ขั้นตอนต่อไปคือการให้คอมพิวเตอร์รับรู้ว่าสิ่งเหล่านี้เป็นคลัสเตอร์แยกกันและติดป้ายแต่ละพิกเซลอย่างถูกต้องด้วยหมายเลข ID สมาชิกภาพของคลัสเตอร์

สำหรับงานนี้ผมเลือกDBSCAN มีการเปรียบเทียบภาพที่ดีงามของวิธี DBSCAN โดยปกติจะทำงานสัมพันธ์กับขั้นตอนวิธีการจัดกลุ่มอื่น ๆ ที่มีอยู่ที่นี่ อย่างที่ฉันพูดไปก่อนหน้านี้มันใช้ได้ดีกับรูปร่างที่ไม่แน่นอน เอาต์พุตของ DBSCAN ซึ่งแต่ละคลัสเตอร์ถูกพล็อตด้วยสีที่แตกต่างกันจะแสดงที่นี่:

เอาต์พุตการทำคลัสเตอร์ DBSCAN

มีบางสิ่งที่ต้องระวังเมื่อดูผลลัพธ์นี้ สิ่งแรกคือ DBSCAN ต้องการให้ผู้ใช้ตั้งค่าพารามิเตอร์ "ความใกล้ชิด" เพื่อควบคุมพฤติกรรมซึ่งควบคุมวิธีการแยกจุดคู่ได้อย่างมีประสิทธิภาพเพื่อให้อัลกอริทึมในการประกาศคลัสเตอร์แยกใหม่แทนที่จะรวมตัวกันเป็นจุดทดสอบบน คลัสเตอร์ที่มีอยู่แล้ว ฉันตั้งค่านี้เป็น 0.04 เท่าของขนาดตามแนวทแยงมุมของแต่ละภาพ เนื่องจากภาพมีขนาดแตกต่างกันไปตั้งแต่ประมาณ VGA ไปจนถึง HD 1080 คำจำกัดความที่เกี่ยวข้องกับสเกลประเภทนี้จึงมีความสำคัญ

จุดที่น่าสังเกตอีกอย่างคืออัลกอริทึม DBSCAN ที่นำมาใช้ใน scikit-Learn นั้นมีขีด จำกัด หน่วยความจำซึ่งค่อนข้างท้าทายสำหรับรูปภาพขนาดใหญ่ในตัวอย่างนี้ ดังนั้นสำหรับภาพขนาดใหญ่บางภาพฉันต้อง "decimate" (เช่นเก็บทุก ๆ พิกเซลที่ 3 หรือ 4 และวางอื่น ๆ ) แต่ละคลัสเตอร์เพื่อให้อยู่ในขีด จำกัด นี้ ผลที่ตามมาของขั้นตอนการคัดเลือกนี้จะทำให้พิกเซลที่กระจัดกระจายของแต่ละพิกเซลที่เหลืออยู่นั้นดูได้ยากในบางภาพที่มีขนาดใหญ่ขึ้น ดังนั้นเพื่อจุดประสงค์ในการแสดงผลเท่านั้นพิกเซลที่มีรหัสสีในภาพด้านบนจึง "ขยาย" ได้อย่างมีประสิทธิภาพเพียงเล็กน้อยเพื่อให้โดดเด่นขึ้น เป็นการดำเนินการด้านเครื่องสำอางอย่างแท้จริงเพื่อประโยชน์ในการเล่าเรื่อง แม้ว่าจะมีความคิดเห็นที่กล่าวถึงการขยายนี้ในรหัสของฉัน

เมื่อมีการระบุกลุ่มและติดป้ายกลุ่มแล้วขั้นตอนที่สามและขั้นสุดท้ายก็ง่าย: ฉันเพียงนำกลุ่มที่ใหญ่ที่สุดในแต่ละภาพ (ในกรณีนี้ฉันเลือกที่จะวัด "ขนาด" ในแง่ของจำนวนพิกเซลสมาชิกแม้ว่าจะทำได้ ให้ใช้ตัวชี้วัดบางอย่างที่วัดขอบเขตทางกายภาพ) และคำนวณฮัลล์นูนสำหรับคลัสเตอร์นั้น ตัวเรือนูนนั้นกลายเป็นเส้นขอบต้นไม้ ลำตัวนูนหกตัวที่คำนวณด้วยวิธีนี้แสดงอยู่ด้านล่างเป็นสีแดง:

ต้นคริสต์มาสที่มีเส้นขอบที่คำนวณได้

รหัสที่มาถูกเขียนขึ้นสำหรับ Python 2.7.6 และมันขึ้นอยู่กับnumpy , SciPy , matplotlibและscikit เรียนรู้ ฉันแบ่งมันเป็นสองส่วน ส่วนแรกรับผิดชอบการประมวลผลภาพจริง:

from PIL import Image
import numpy as np
import scipy as sp
import matplotlib.colors as colors
from sklearn.cluster import DBSCAN
from math import ceil, sqrt

"""
Inputs:

    rgbimg:         [M,N,3] numpy array containing (uint, 0-255) color image

    hueleftthr:     Scalar constant to select maximum allowed hue in the
                    yellow-green region

    huerightthr:    Scalar constant to select minimum allowed hue in the
                    blue-purple region

    satthr:         Scalar constant to select minimum allowed saturation

    valthr:         Scalar constant to select minimum allowed value

    monothr:        Scalar constant to select minimum allowed monochrome
                    brightness

    maxpoints:      Scalar constant maximum number of pixels to forward to
                    the DBSCAN clustering algorithm

    proxthresh:     Proximity threshold to use for DBSCAN, as a fraction of
                    the diagonal size of the image

Outputs:

    borderseg:      [K,2,2] Nested list containing K pairs of x- and y- pixel
                    values for drawing the tree border

    X:              [P,2] List of pixels that passed the threshold step

    labels:         [Q,2] List of cluster labels for points in Xslice (see
                    below)

    Xslice:         [Q,2] Reduced list of pixels to be passed to DBSCAN

"""

def findtree(rgbimg, hueleftthr=0.2, huerightthr=0.95, satthr=0.7, 
             valthr=0.7, monothr=220, maxpoints=5000, proxthresh=0.04):

    # Convert rgb image to monochrome for
    gryimg = np.asarray(Image.fromarray(rgbimg).convert('L'))
    # Convert rgb image (uint, 0-255) to hsv (float, 0.0-1.0)
    hsvimg = colors.rgb_to_hsv(rgbimg.astype(float)/255)

    # Initialize binary thresholded image
    binimg = np.zeros((rgbimg.shape[0], rgbimg.shape[1]))
    # Find pixels with hue<0.2 or hue>0.95 (red or yellow) and saturation/value
    # both greater than 0.7 (saturated and bright)--tends to coincide with
    # ornamental lights on trees in some of the images
    boolidx = np.logical_and(
                np.logical_and(
                  np.logical_or((hsvimg[:,:,0] < hueleftthr),
                                (hsvimg[:,:,0] > huerightthr)),
                                (hsvimg[:,:,1] > satthr)),
                                (hsvimg[:,:,2] > valthr))
    # Find pixels that meet hsv criterion
    binimg[np.where(boolidx)] = 255
    # Add pixels that meet grayscale brightness criterion
    binimg[np.where(gryimg > monothr)] = 255

    # Prepare thresholded points for DBSCAN clustering algorithm
    X = np.transpose(np.where(binimg == 255))
    Xslice = X
    nsample = len(Xslice)
    if nsample > maxpoints:
        # Make sure number of points does not exceed DBSCAN maximum capacity
        Xslice = X[range(0,nsample,int(ceil(float(nsample)/maxpoints)))]

    # Translate DBSCAN proximity threshold to units of pixels and run DBSCAN
    pixproxthr = proxthresh * sqrt(binimg.shape[0]**2 + binimg.shape[1]**2)
    db = DBSCAN(eps=pixproxthr, min_samples=10).fit(Xslice)
    labels = db.labels_.astype(int)

    # Find the largest cluster (i.e., with most points) and obtain convex hull   
    unique_labels = set(labels)
    maxclustpt = 0
    for k in unique_labels:
        class_members = [index[0] for index in np.argwhere(labels == k)]
        if len(class_members) > maxclustpt:
            points = Xslice[class_members]
            hull = sp.spatial.ConvexHull(points)
            maxclustpt = len(class_members)
            borderseg = [[points[simplex,0], points[simplex,1]] for simplex
                          in hull.simplices]

    return borderseg, X, labels, Xslice

และส่วนที่สองเป็นสคริปต์ระดับผู้ใช้ซึ่งเรียกไฟล์แรกและสร้างแปลงทั้งหมดข้างต้น:

#!/usr/bin/env python

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from findtree import findtree

# Image files to process
fname = ['nmzwj.png', 'aVZhC.png', '2K9EF.png',
         'YowlH.png', '2y4o5.png', 'FWhSP.png']

# Initialize figures
fgsz = (16,7)        
figthresh = plt.figure(figsize=fgsz, facecolor='w')
figclust  = plt.figure(figsize=fgsz, facecolor='w')
figcltwo  = plt.figure(figsize=fgsz, facecolor='w')
figborder = plt.figure(figsize=fgsz, facecolor='w')
figthresh.canvas.set_window_title('Thresholded HSV and Monochrome Brightness')
figclust.canvas.set_window_title('DBSCAN Clusters (Raw Pixel Output)')
figcltwo.canvas.set_window_title('DBSCAN Clusters (Slightly Dilated for Display)')
figborder.canvas.set_window_title('Trees with Borders')

for ii, name in zip(range(len(fname)), fname):
    # Open the file and convert to rgb image
    rgbimg = np.asarray(Image.open(name))

    # Get the tree borders as well as a bunch of other intermediate values
    # that will be used to illustrate how the algorithm works
    borderseg, X, labels, Xslice = findtree(rgbimg)

    # Display thresholded images
    axthresh = figthresh.add_subplot(2,3,ii+1)
    axthresh.set_xticks([])
    axthresh.set_yticks([])
    binimg = np.zeros((rgbimg.shape[0], rgbimg.shape[1]))
    for v, h in X:
        binimg[v,h] = 255
    axthresh.imshow(binimg, interpolation='nearest', cmap='Greys')

    # Display color-coded clusters
    axclust = figclust.add_subplot(2,3,ii+1) # Raw version
    axclust.set_xticks([])
    axclust.set_yticks([])
    axcltwo = figcltwo.add_subplot(2,3,ii+1) # Dilated slightly for display only
    axcltwo.set_xticks([])
    axcltwo.set_yticks([])
    axcltwo.imshow(binimg, interpolation='nearest', cmap='Greys')
    clustimg = np.ones(rgbimg.shape)    
    unique_labels = set(labels)
    # Generate a unique color for each cluster 
    plcol = cm.rainbow_r(np.linspace(0, 1, len(unique_labels)))
    for lbl, pix in zip(labels, Xslice):
        for col, unqlbl in zip(plcol, unique_labels):
            if lbl == unqlbl:
                # Cluster label of -1 indicates no cluster membership;
                # override default color with black
                if lbl == -1:
                    col = [0.0, 0.0, 0.0, 1.0]
                # Raw version
                for ij in range(3):
                    clustimg[pix[0],pix[1],ij] = col[ij]
                # Dilated just for display
                axcltwo.plot(pix[1], pix[0], 'o', markerfacecolor=col, 
                    markersize=1, markeredgecolor=col)
    axclust.imshow(clustimg)
    axcltwo.set_xlim(0, binimg.shape[1]-1)
    axcltwo.set_ylim(binimg.shape[0], -1)

    # Plot original images with read borders around the trees
    axborder = figborder.add_subplot(2,3,ii+1)
    axborder.set_axis_off()
    axborder.imshow(rgbimg, interpolation='nearest')
    for vseg, hseg in borderseg:
        axborder.plot(hseg, vseg, 'r-', lw=3)
    axborder.set_xlim(0, binimg.shape[1]-1)
    axborder.set_ylim(binimg.shape[0], -1)

plt.show()

โซลูชันของ @ lennon310 กำลังรวมกลุ่ม (k-
mean

1
@stachyra ฉันยังคิดเกี่ยวกับวิธีการนี้ก่อนที่จะเสนอวิธีที่ง่ายกว่าของฉัน ฉันคิดว่านี่เป็นโอกาสที่ดีในการขยายและวางหลักเกณฑ์ทั่วไปเพื่อให้ได้ผลลัพธ์ที่ดีในกรณีอื่น ๆ คุณสามารถทดลองใช้มุ้งประสาทสำหรับการทำคลัสเตอร์ บางอย่างเช่น SOM หรือก๊าซประสาทจะทำงานได้ดี อย่างไรก็ตามข้อเสนอที่ดีและยกนิ้วให้ฉัน!
sepdek

4
@Faust & Ryan Carlson: ขอบคุณพวก! ใช่ฉันยอมรับว่าระบบ upvote ในขณะที่มันทำงานได้ดีสำหรับการตัดสินระหว่าง 2 หรือ 3 คำตอบสั้น ๆ ทั้งหมดที่ส่งภายในไม่กี่ชั่วโมงของกันและกันมีอคติที่รุนแรงเมื่อมันมาถึงการแข่งขันกับคำตอบยาวที่เล่นออกมาเป็นระยะเวลานาน . สำหรับสิ่งหนึ่งที่การส่งก่อนเริ่มสะสม upvotes ก่อนที่จะมีการตรวจสอบสาธารณะ และถ้าคำตอบนั้นยาวมากแล้วทันทีที่ใครสร้างผู้นำที่เจียมเนื้อเจียมตัวก็มักจะมี "เอฟเฟกต์แบนด์แวกอน" เพราะผู้คนโหวตขึ้นคนแรกเท่านั้นโดยไม่รบกวนอ่านส่วนที่เหลือ
stachyra

2
@stachyra เพื่อนข่าวดี! ขอแสดงความยินดีอย่างอบอุ่นและนี่อาจเป็นจุดเริ่มต้นสำหรับปีใหม่ของคุณ!
sepdek

1
@ lennon310: ฉันยังไม่ได้ลองใช้ตัวกรองการตรวจหาสูงสุดในเครื่องในปัญหานี้ แต่ถ้าคุณต้องการสำรวจด้วยตัวเอง scipy จะรวมตัวนี้ไว้ รหัสแหล่ง Python ของฉันสำหรับโครงการนี้สั้นมากจนฉันสามารถเผยแพร่ได้ 100% แท้จริงทั้งหมดที่คุณต้องทำคือคัดลอกและวางโค้ดสองชุดของฉันเป็นไฟล์. py แยกต่างหากจากนั้นแทนการโทรไปยังscipy.ndimage.filters.maximum_filter()ที่เดียวกับที่ฉันใช้ธรณีประตู
stachyra

145

หมายเหตุการแก้ไข:ฉันแก้ไขโพสต์นี้ไปที่ (i) ประมวลผลภาพต้นไม้แต่ละภาพตามที่ร้องขอในข้อกำหนด (ii) เพื่อพิจารณาทั้งความสว่างและรูปร่างของวัตถุเพื่อปรับปรุงคุณภาพของผลลัพธ์


ด้านล่างนี้นำเสนอวิธีการที่คำนึงถึงความสว่างและรูปร่างของวัตถุ กล่าวอีกนัยหนึ่งมันจะค้นหาวัตถุที่มีรูปร่างคล้ายสามเหลี่ยมและมีความสว่างอย่างมีนัยสำคัญ มันถูกนำมาใช้ใน Java โดยใช้กรอบการประมวลผลภาพของMarvin

ขั้นตอนแรกคือการนวดข้าวสี วัตถุประสงค์นี่คือเพื่อมุ่งเน้นการวิเคราะห์วัตถุที่มีความสว่างอย่างมีนัยสำคัญ

ภาพเอาท์พุท:

รหัสแหล่งที่มา:

public class ChristmasTree {

private MarvinImagePlugin fill = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.fill.boundaryFill");
private MarvinImagePlugin threshold = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.thresholding");
private MarvinImagePlugin invert = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.invert");
private MarvinImagePlugin dilation = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.morphological.dilation");

public ChristmasTree(){
    MarvinImage tree;

    // Iterate each image
    for(int i=1; i<=6; i++){
        tree = MarvinImageIO.loadImage("./res/trees/tree"+i+".png");

        // 1. Threshold
        threshold.setAttribute("threshold", 200);
        threshold.process(tree.clone(), tree);
    }
}
public static void main(String[] args) {
    new ChristmasTree();
}
}

ในขั้นตอนที่สองจุดที่สว่างที่สุดในภาพจะถูกขยายเพื่อสร้างรูปร่าง ผลลัพธ์ของกระบวนการนี้คือรูปร่างที่น่าจะเป็นของวัตถุที่มีความสว่างอย่างมีนัยสำคัญ ใช้การแบ่งส่วนเติมน้ำท่วมตรวจพบการเชื่อมต่อรูปร่าง

ภาพเอาท์พุท:

รหัสแหล่งที่มา:

public class ChristmasTree {

private MarvinImagePlugin fill = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.fill.boundaryFill");
private MarvinImagePlugin threshold = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.thresholding");
private MarvinImagePlugin invert = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.invert");
private MarvinImagePlugin dilation = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.morphological.dilation");

public ChristmasTree(){
    MarvinImage tree;

    // Iterate each image
    for(int i=1; i<=6; i++){
        tree = MarvinImageIO.loadImage("./res/trees/tree"+i+".png");

        // 1. Threshold
        threshold.setAttribute("threshold", 200);
        threshold.process(tree.clone(), tree);

        // 2. Dilate
        invert.process(tree.clone(), tree);
        tree = MarvinColorModelConverter.rgbToBinary(tree, 127);
        MarvinImageIO.saveImage(tree, "./res/trees/new/tree_"+i+"threshold.png");
        dilation.setAttribute("matrix", MarvinMath.getTrueMatrix(50, 50));
        dilation.process(tree.clone(), tree);
        MarvinImageIO.saveImage(tree, "./res/trees/new/tree_"+1+"_dilation.png");
        tree = MarvinColorModelConverter.binaryToRgb(tree);

        // 3. Segment shapes
        MarvinImage trees2 = tree.clone();
        fill(tree, trees2);
        MarvinImageIO.saveImage(trees2, "./res/trees/new/tree_"+i+"_fill.png");
}

private void fill(MarvinImage imageIn, MarvinImage imageOut){
    boolean found;
    int color= 0xFFFF0000;

    while(true){
        found=false;

        Outerloop:
        for(int y=0; y<imageIn.getHeight(); y++){
            for(int x=0; x<imageIn.getWidth(); x++){
                if(imageOut.getIntComponent0(x, y) == 0){
                    fill.setAttribute("x", x);
                    fill.setAttribute("y", y);
                    fill.setAttribute("color", color);
                    fill.setAttribute("threshold", 120);
                    fill.process(imageIn, imageOut);
                    color = newColor(color);

                    found = true;
                    break Outerloop;
                }
            }
        }

        if(!found){
            break;
        }
    }

}

private int newColor(int color){
    int red = (color & 0x00FF0000) >> 16;
    int green = (color & 0x0000FF00) >> 8;
    int blue = (color & 0x000000FF);

    if(red <= green && red <= blue){
        red+=5;
    }
    else if(green <= red && green <= blue){
        green+=5;
    }
    else{
        blue+=5;
    }

    return 0xFF000000 + (red << 16) + (green << 8) + blue;
}

public static void main(String[] args) {
    new ChristmasTree();
}
}

ดังที่แสดงในรูปภาพผลลัพธ์ตรวจพบรูปร่างหลายรูปร่าง ในปัญหานี้มีจุดสว่างเพียงไม่กี่จุดในภาพ อย่างไรก็ตามวิธีนี้ถูกนำมาใช้เพื่อจัดการกับสถานการณ์ที่ซับซ้อนมากขึ้น

ในขั้นตอนถัดไปแต่ละรูปร่างจะถูกวิเคราะห์ อัลกอริทึมง่าย ๆ ตรวจจับรูปร่างที่มีรูปแบบคล้ายกับรูปสามเหลี่ยม อัลกอริทึมวิเคราะห์รูปร่างวัตถุทีละบรรทัด หากจุดศูนย์กลางของมวลของเส้นรูปร่างแต่ละเส้นนั้นใกล้เคียงกัน (กำหนดขีด จำกัด ) และเพิ่มมวลเมื่อเพิ่มขึ้น y วัตถุนั้นจะมีรูปร่างเป็นรูปสามเหลี่ยม มวลของเส้นรูปร่างคือจำนวนพิกเซลในบรรทัดนั้นที่เป็นของรูปร่าง จินตนาการว่าคุณหั่นวัตถุในแนวนอนและวิเคราะห์แต่ละส่วนในแนวนอน หากมีการรวมศูนย์กันและความยาวเพิ่มขึ้นจากส่วนแรกไปยังส่วนสุดท้ายในรูปแบบเชิงเส้นคุณอาจมีวัตถุที่มีลักษณะคล้ายกับรูปสามเหลี่ยม

รหัสแหล่งที่มา:

private int[] detectTrees(MarvinImage image){
    HashSet<Integer> analysed = new HashSet<Integer>();
    boolean found;
    while(true){
        found = false;
        for(int y=0; y<image.getHeight(); y++){
            for(int x=0; x<image.getWidth(); x++){
                int color = image.getIntColor(x, y);

                if(!analysed.contains(color)){
                    if(isTree(image, color)){
                        return getObjectRect(image, color);
                    }

                    analysed.add(color);
                    found=true;
                }
            }
        }

        if(!found){
            break;
        }
    }
    return null;
}

private boolean isTree(MarvinImage image, int color){

    int mass[][] = new int[image.getHeight()][2];
    int yStart=-1;
    int xStart=-1;
    for(int y=0; y<image.getHeight(); y++){
        int mc = 0;
        int xs=-1;
        int xe=-1;
        for(int x=0; x<image.getWidth(); x++){
            if(image.getIntColor(x, y) == color){
                mc++;

                if(yStart == -1){
                    yStart=y;
                    xStart=x;
                }

                if(xs == -1){
                    xs = x;
                }
                if(x > xe){
                    xe = x;
                }
            }
        }
        mass[y][0] = xs;
        mass[y][3] = xe;
        mass[y][4] = mc;    
    }

    int validLines=0;
    for(int y=0; y<image.getHeight(); y++){
        if
        ( 
            mass[y][5] > 0 &&
            Math.abs(((mass[y][0]+mass[y][6])/2)-xStart) <= 50 &&
            mass[y][7] >= (mass[yStart][8] + (y-yStart)*0.3) &&
            mass[y][9] <= (mass[yStart][10] + (y-yStart)*1.5)
        )
        {
            validLines++;
        }
    }

    if(validLines > 100){
        return true;
    }
    return false;
}

ในที่สุดตำแหน่งของแต่ละรูปร่างคล้ายกับรูปสามเหลี่ยมและด้วยความสว่างที่สำคัญในกรณีนี้ต้นคริสต์มาสจะถูกเน้นในภาพต้นฉบับดังที่แสดงด้านล่าง

ภาพที่ออกสุดท้าย:

รหัสที่มาสุดท้าย:

public class ChristmasTree {

private MarvinImagePlugin fill = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.fill.boundaryFill");
private MarvinImagePlugin threshold = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.thresholding");
private MarvinImagePlugin invert = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.invert");
private MarvinImagePlugin dilation = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.morphological.dilation");

public ChristmasTree(){
    MarvinImage tree;

    // Iterate each image
    for(int i=1; i<=6; i++){
        tree = MarvinImageIO.loadImage("./res/trees/tree"+i+".png");

        // 1. Threshold
        threshold.setAttribute("threshold", 200);
        threshold.process(tree.clone(), tree);

        // 2. Dilate
        invert.process(tree.clone(), tree);
        tree = MarvinColorModelConverter.rgbToBinary(tree, 127);
        MarvinImageIO.saveImage(tree, "./res/trees/new/tree_"+i+"threshold.png");
        dilation.setAttribute("matrix", MarvinMath.getTrueMatrix(50, 50));
        dilation.process(tree.clone(), tree);
        MarvinImageIO.saveImage(tree, "./res/trees/new/tree_"+1+"_dilation.png");
        tree = MarvinColorModelConverter.binaryToRgb(tree);

        // 3. Segment shapes
        MarvinImage trees2 = tree.clone();
        fill(tree, trees2);
        MarvinImageIO.saveImage(trees2, "./res/trees/new/tree_"+i+"_fill.png");

        // 4. Detect tree-like shapes
        int[] rect = detectTrees(trees2);

        // 5. Draw the result
        MarvinImage original = MarvinImageIO.loadImage("./res/trees/tree"+i+".png");
        drawBoundary(trees2, original, rect);
        MarvinImageIO.saveImage(original, "./res/trees/new/tree_"+i+"_out_2.jpg");
    }
}

private void drawBoundary(MarvinImage shape, MarvinImage original, int[] rect){
    int yLines[] = new int[6];
    yLines[0] = rect[1];
    yLines[1] = rect[1]+(int)((rect[3]/5));
    yLines[2] = rect[1]+((rect[3]/5)*2);
    yLines[3] = rect[1]+((rect[3]/5)*3);
    yLines[4] = rect[1]+(int)((rect[3]/5)*4);
    yLines[5] = rect[1]+rect[3];

    List<Point> points = new ArrayList<Point>();
    for(int i=0; i<yLines.length; i++){
        boolean in=false;
        Point startPoint=null;
        Point endPoint=null;
        for(int x=rect[0]; x<rect[0]+rect[2]; x++){

            if(shape.getIntColor(x, yLines[i]) != 0xFFFFFFFF){
                if(!in){
                    if(startPoint == null){
                        startPoint = new Point(x, yLines[i]);
                    }
                }
                in = true;
            }
            else{
                if(in){
                    endPoint = new Point(x, yLines[i]);
                }
                in = false;
            }
        }

        if(endPoint == null){
            endPoint = new Point((rect[0]+rect[2])-1, yLines[i]);
        }

        points.add(startPoint);
        points.add(endPoint);
    }

    drawLine(points.get(0).x, points.get(0).y, points.get(1).x, points.get(1).y, 15, original);
    drawLine(points.get(1).x, points.get(1).y, points.get(3).x, points.get(3).y, 15, original);
    drawLine(points.get(3).x, points.get(3).y, points.get(5).x, points.get(5).y, 15, original);
    drawLine(points.get(5).x, points.get(5).y, points.get(7).x, points.get(7).y, 15, original);
    drawLine(points.get(7).x, points.get(7).y, points.get(9).x, points.get(9).y, 15, original);
    drawLine(points.get(9).x, points.get(9).y, points.get(11).x, points.get(11).y, 15, original);
    drawLine(points.get(11).x, points.get(11).y, points.get(10).x, points.get(10).y, 15, original);
    drawLine(points.get(10).x, points.get(10).y, points.get(8).x, points.get(8).y, 15, original);
    drawLine(points.get(8).x, points.get(8).y, points.get(6).x, points.get(6).y, 15, original);
    drawLine(points.get(6).x, points.get(6).y, points.get(4).x, points.get(4).y, 15, original);
    drawLine(points.get(4).x, points.get(4).y, points.get(2).x, points.get(2).y, 15, original);
    drawLine(points.get(2).x, points.get(2).y, points.get(0).x, points.get(0).y, 15, original);
}

private void drawLine(int x1, int y1, int x2, int y2, int length, MarvinImage image){
    int lx1, lx2, ly1, ly2;
    for(int i=0; i<length; i++){
        lx1 = (x1+i >= image.getWidth() ? (image.getWidth()-1)-i: x1);
        lx2 = (x2+i >= image.getWidth() ? (image.getWidth()-1)-i: x2);
        ly1 = (y1+i >= image.getHeight() ? (image.getHeight()-1)-i: y1);
        ly2 = (y2+i >= image.getHeight() ? (image.getHeight()-1)-i: y2);

        image.drawLine(lx1+i, ly1, lx2+i, ly2, Color.red);
        image.drawLine(lx1, ly1+i, lx2, ly2+i, Color.red);
    }
}

private void fillRect(MarvinImage image, int[] rect, int length){
    for(int i=0; i<length; i++){
        image.drawRect(rect[0]+i, rect[1]+i, rect[2]-(i*2), rect[3]-(i*2), Color.red);
    }
}

private void fill(MarvinImage imageIn, MarvinImage imageOut){
    boolean found;
    int color= 0xFFFF0000;

    while(true){
        found=false;

        Outerloop:
        for(int y=0; y<imageIn.getHeight(); y++){
            for(int x=0; x<imageIn.getWidth(); x++){
                if(imageOut.getIntComponent0(x, y) == 0){
                    fill.setAttribute("x", x);
                    fill.setAttribute("y", y);
                    fill.setAttribute("color", color);
                    fill.setAttribute("threshold", 120);
                    fill.process(imageIn, imageOut);
                    color = newColor(color);

                    found = true;
                    break Outerloop;
                }
            }
        }

        if(!found){
            break;
        }
    }

}

private int[] detectTrees(MarvinImage image){
    HashSet<Integer> analysed = new HashSet<Integer>();
    boolean found;
    while(true){
        found = false;
        for(int y=0; y<image.getHeight(); y++){
            for(int x=0; x<image.getWidth(); x++){
                int color = image.getIntColor(x, y);

                if(!analysed.contains(color)){
                    if(isTree(image, color)){
                        return getObjectRect(image, color);
                    }

                    analysed.add(color);
                    found=true;
                }
            }
        }

        if(!found){
            break;
        }
    }
    return null;
}

private boolean isTree(MarvinImage image, int color){

    int mass[][] = new int[image.getHeight()][11];
    int yStart=-1;
    int xStart=-1;
    for(int y=0; y<image.getHeight(); y++){
        int mc = 0;
        int xs=-1;
        int xe=-1;
        for(int x=0; x<image.getWidth(); x++){
            if(image.getIntColor(x, y) == color){
                mc++;

                if(yStart == -1){
                    yStart=y;
                    xStart=x;
                }

                if(xs == -1){
                    xs = x;
                }
                if(x > xe){
                    xe = x;
                }
            }
        }
        mass[y][0] = xs;
        mass[y][12] = xe;
        mass[y][13] = mc;   
    }

    int validLines=0;
    for(int y=0; y<image.getHeight(); y++){
        if
        ( 
            mass[y][14] > 0 &&
            Math.abs(((mass[y][0]+mass[y][15])/2)-xStart) <= 50 &&
            mass[y][16] >= (mass[yStart][17] + (y-yStart)*0.3) &&
            mass[y][18] <= (mass[yStart][19] + (y-yStart)*1.5)
        )
        {
            validLines++;
        }
    }

    if(validLines > 100){
        return true;
    }
    return false;
}

private int[] getObjectRect(MarvinImage image, int color){
    int x1=-1;
    int x2=-1;
    int y1=-1;
    int y2=-1;

    for(int y=0; y<image.getHeight(); y++){
        for(int x=0; x<image.getWidth(); x++){
            if(image.getIntColor(x, y) == color){

                if(x1 == -1 || x < x1){
                    x1 = x;
                }
                if(x2 == -1 || x > x2){
                    x2 = x;
                }
                if(y1 == -1 || y < y1){
                    y1 = y;
                }
                if(y2 == -1 || y > y2){
                    y2 = y;
                }
            }
        }
    }

    return new int[]{x1, y1, (x2-x1), (y2-y1)};
}

private int newColor(int color){
    int red = (color & 0x00FF0000) >> 16;
    int green = (color & 0x0000FF00) >> 8;
    int blue = (color & 0x000000FF);

    if(red <= green && red <= blue){
        red+=5;
    }
    else if(green <= red && green <= blue){
        green+=30;
    }
    else{
        blue+=30;
    }

    return 0xFF000000 + (red << 16) + (green << 8) + blue;
}

public static void main(String[] args) {
    new ChristmasTree();
}
}

ข้อดีของวิธีนี้คือความจริงที่ว่ามันอาจจะทำงานกับภาพที่มีวัตถุเรืองแสงอื่น ๆ ได้เนื่องจากมันจะทำการวิเคราะห์รูปร่างของวัตถุ

สุขสันต์วันคริสต์มาส!


แก้ไขหมายเหตุ 2

มีการอภิปรายเกี่ยวกับความคล้ายคลึงกันของอิมเมจเอาต์พุตของโซลูชันนี้และอื่น ๆ ในความเป็นจริงมันคล้ายกันมาก แต่วิธีการนี้ไม่เพียง แต่แบ่งเซ็กเมนต์วัตถุ นอกจากนี้ยังวิเคราะห์รูปร่างของวัตถุในบางแง่ มันสามารถจัดการวัตถุเรืองแสงได้หลายฉากในฉากเดียวกัน ที่จริงแล้วต้นคริสต์มาสไม่จำเป็นต้องเป็นต้นคริสต์มาสที่สว่างที่สุด ฉันแค่ทำมันเพื่อเสริมสร้างการสนทนา มีอคติในตัวอย่างที่มองหาวัตถุที่สว่างที่สุดคุณจะพบต้นไม้ แต่เราต้องการหยุดการสนทนาจริงๆหรือไม่? ณ จุดนี้คอมพิวเตอร์จะจำวัตถุที่คล้ายกับต้นคริสต์มาสได้ไกลแค่ไหน ลองปิดช่องว่างนี้ก่อน

ด้านล่างนี้จะแสดงผลลัพธ์เพื่ออธิบายประเด็นนี้:

ภาพอินพุต

ป้อนคำอธิบายรูปภาพที่นี่

เอาท์พุต

ป้อนคำอธิบายรูปภาพที่นี่


2
นั่นดูน่าสนใจ. ฉันหวังว่าคุณจะได้รับผลลัพธ์เดียวกันเมื่อประมวลผลแต่ละภาพ ฉันแก้ไขคำถาม 4 ชั่วโมงก่อนหน้านี้ให้คุณโพสต์คำตอบเพื่อระบุสิ่งนี้โดยเฉพาะ มันจะยอดเยี่ยมถ้าคุณสามารถปรับปรุงคำตอบของคุณด้วยผลลัพธ์เหล่านี้
karlphillip

@Marvin ในการตรวจจับสามเหลี่ยมของคุณคุณจัดการกับความผันผวนของมวลอย่างไร? มันไม่ได้เป็นรูปสามเหลี่ยมที่เข้มงวดมวลไม่ได้เป็นโมโนเมื่อมีการเปลี่ยนแปลง y
3054997

2
@ user3054997: นั่นเป็นอีกจุดหนึ่ง ขณะที่ฉันโพสต์อัลกอริทึมจะไม่ค้นหารูปสามเหลี่ยมที่เข้มงวด มันวิเคราะห์แต่ละวัตถุและพิจารณาต้นไม้ที่ "คล้าย" รูปสามเหลี่ยมที่มีเกณฑ์ง่าย ๆ : มวลของวัตถุนั้นถูกใช้เพื่อเพิ่มเมื่อ y เพิ่มขึ้นและศูนย์กลางของมวลของส่วนวัตถุแนวนอนเกือบทั้งหมดจะรวมศูนย์กัน .
Gabriel Ambrósio Archanjo

@ Marvin วิธีการแก้ปัญหาของฉันง่ายมากฉันได้ระบุไว้ในคำตอบของฉันด้วย โดยวิธีการทำงานดีกว่าโซลูชั่นแรกของคุณ ถ้าฉันจำได้อย่างถูกต้องในคำตอบแรกของคุณคุณได้พูดคุยเกี่ยวกับตัวบอกคุณสมบัติเพื่อตรวจจับแสงพื้นผิวขนาดเล็กซึ่งไม่ใช่สิ่งที่คุณกำลังทำอยู่ที่นี่ ฉันเพียงแค่บอกว่าวิธีการและผลลัพธ์ในปัจจุบันของคุณนั้นคล้ายคลึงกับของฉันมากกว่าวิธีแรกของคุณ แน่นอนฉันไม่คาดหวังว่าคุณจะยอมรับฉันระบุไว้เพียงเพื่อบันทึก
smeso

1
@sepdek ที่นี่มีวิธีแก้ปัญหาที่ดีกว่าของฉันจริงๆและพวกเขายังได้รับ upvotes ครึ่งหนึ่งของฉัน ไม่มีอะไรผิดปกติในการ "รับแรงบันดาลใจ" จากโซลูชันอื่น ๆ ฉันเห็นวิธีแก้ปัญหาของคุณเช่นกันฉันไม่มีอะไรจะพูดกับคุณคุณโพสต์พวกเขาหลังจากฉันและ "ความคิด" ของฉันก็ไม่ได้เป็นต้นฉบับที่จะบอกว่าคุณเพิ่งคัดลอกฉัน แต่มาร์วินเป็นคนเดียวที่โพสต์ต่อหน้าฉันและแก้ไขเป็นวิธีแก้ปัญหาหลังจากเห็นฉันใช้อัลกอริทึมเดียวกัน ... อย่างน้อยเขาก็บอกว่า "ใช่ฉันชอบโซลูชันของคุณและฉันนำกลับมาใช้ใหม่" ไม่มีอะไรผิดปกติ เกม.
smeso

75

นี่คือทางออกที่ง่ายและเป็นใบ้ของฉัน มันขึ้นอยู่กับข้อสันนิษฐานว่าต้นไม้จะเป็นสิ่งที่สว่างและใหญ่ที่สุดในภาพ

//g++ -Wall -pedantic -ansi -O2 -pipe -s -o christmas_tree christmas_tree.cpp `pkg-config --cflags --libs opencv`
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int argc,char *argv[])
{
    Mat original,tmp,tmp1;
    vector <vector<Point> > contours;
    Moments m;
    Rect boundrect;
    Point2f center;
    double radius, max_area=0,tmp_area=0;
    unsigned int j, k;
    int i;

    for(i = 1; i < argc; ++i)
    {
        original = imread(argv[i]);
        if(original.empty())
        {
            cerr << "Error"<<endl;
            return -1;
        }

        GaussianBlur(original, tmp, Size(3, 3), 0, 0, BORDER_DEFAULT);
        erode(tmp, tmp, Mat(), Point(-1, -1), 10);
        cvtColor(tmp, tmp, CV_BGR2HSV);
        inRange(tmp, Scalar(0, 0, 0), Scalar(180, 255, 200), tmp);

        dilate(original, tmp1, Mat(), Point(-1, -1), 15);
        cvtColor(tmp1, tmp1, CV_BGR2HLS);
        inRange(tmp1, Scalar(0, 185, 0), Scalar(180, 255, 255), tmp1);
        dilate(tmp1, tmp1, Mat(), Point(-1, -1), 10);

        bitwise_and(tmp, tmp1, tmp1);

        findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
        max_area = 0;
        j = 0;
        for(k = 0; k < contours.size(); k++)
        {
            tmp_area = contourArea(contours[k]);
            if(tmp_area > max_area)
            {
                max_area = tmp_area;
                j = k;
            }
        }
        tmp1 = Mat::zeros(original.size(),CV_8U);
        approxPolyDP(contours[j], contours[j], 30, true);
        drawContours(tmp1, contours, j, Scalar(255,255,255), CV_FILLED);

        m = moments(contours[j]);
        boundrect = boundingRect(contours[j]);
        center = Point2f(m.m10/m.m00, m.m01/m.m00);
        radius = (center.y - (boundrect.tl().y))/4.0*3.0;
        Rect heightrect(center.x-original.cols/5, boundrect.tl().y, original.cols/5*2, boundrect.size().height);

        tmp = Mat::zeros(original.size(), CV_8U);
        rectangle(tmp, heightrect, Scalar(255, 255, 255), -1);
        circle(tmp, center, radius, Scalar(255, 255, 255), -1);

        bitwise_and(tmp, tmp1, tmp1);

        findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
        max_area = 0;
        j = 0;
        for(k = 0; k < contours.size(); k++)
        {
            tmp_area = contourArea(contours[k]);
            if(tmp_area > max_area)
            {
                max_area = tmp_area;
                j = k;
            }
        }

        approxPolyDP(contours[j], contours[j], 30, true);
        convexHull(contours[j], contours[j]);

        drawContours(original, contours, j, Scalar(0, 0, 255), 3);

        namedWindow(argv[i], CV_WINDOW_NORMAL|CV_WINDOW_KEEPRATIO|CV_GUI_EXPANDED);
        imshow(argv[i], original);

        waitKey(0);
        destroyWindow(argv[i]);
    }

    return 0;
}

ขั้นตอนแรกคือการตรวจจับพิกเซลที่สว่างที่สุดในภาพ แต่เราต้องแยกความแตกต่างระหว่างต้นไม้กับหิมะที่สะท้อนแสงของมัน ที่นี่เราพยายามแยกหิมะที่ใช้ตัวกรองอย่างง่าย ๆ บนรหัสสี:

GaussianBlur(original, tmp, Size(3, 3), 0, 0, BORDER_DEFAULT);
erode(tmp, tmp, Mat(), Point(-1, -1), 10);
cvtColor(tmp, tmp, CV_BGR2HSV);
inRange(tmp, Scalar(0, 0, 0), Scalar(180, 255, 200), tmp);

จากนั้นเราจะพบทุกพิกเซล "สว่าง":

dilate(original, tmp1, Mat(), Point(-1, -1), 15);
cvtColor(tmp1, tmp1, CV_BGR2HLS);
inRange(tmp1, Scalar(0, 185, 0), Scalar(180, 255, 255), tmp1);
dilate(tmp1, tmp1, Mat(), Point(-1, -1), 10);

ในที่สุดเราก็เข้าร่วมผลการค้นหาทั้งสอง:

bitwise_and(tmp, tmp1, tmp1);

ตอนนี้เรามองหาวัตถุที่สว่างที่สุด:

findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
max_area = 0;
j = 0;
for(k = 0; k < contours.size(); k++)
{
    tmp_area = contourArea(contours[k]);
    if(tmp_area > max_area)
    {
        max_area = tmp_area;
        j = k;
    }
}
tmp1 = Mat::zeros(original.size(),CV_8U);
approxPolyDP(contours[j], contours[j], 30, true);
drawContours(tmp1, contours, j, Scalar(255,255,255), CV_FILLED);

ตอนนี้เราเกือบจะเสร็จแล้ว แต่ก็ยังมีความไม่สมบูรณ์เนื่องจากหิมะ หากต้องการตัดออกเราจะสร้างมาสก์โดยใช้วงกลมและสี่เหลี่ยมผืนผ้าเพื่อประมาณรูปร่างของต้นไม้เพื่อลบส่วนที่ไม่ต้องการ:

m = moments(contours[j]);
boundrect = boundingRect(contours[j]);
center = Point2f(m.m10/m.m00, m.m01/m.m00);
radius = (center.y - (boundrect.tl().y))/4.0*3.0;
Rect heightrect(center.x-original.cols/5, boundrect.tl().y, original.cols/5*2, boundrect.size().height);

tmp = Mat::zeros(original.size(), CV_8U);
rectangle(tmp, heightrect, Scalar(255, 255, 255), -1);
circle(tmp, center, radius, Scalar(255, 255, 255), -1);

bitwise_and(tmp, tmp1, tmp1);

ขั้นตอนสุดท้ายคือการค้นหารูปร่างของต้นไม้และวาดลงบนภาพต้นฉบับ

findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
max_area = 0;
j = 0;
for(k = 0; k < contours.size(); k++)
{
    tmp_area = contourArea(contours[k]);
    if(tmp_area > max_area)
    {
        max_area = tmp_area;
        j = k;
    }
}

approxPolyDP(contours[j], contours[j], 30, true);
convexHull(contours[j], contours[j]);

drawContours(original, contours, j, Scalar(0, 0, 255), 3);

ฉันขอโทษ แต่ในตอนนี้ฉันมีการเชื่อมต่อที่ไม่ดีดังนั้นจึงเป็นไปไม่ได้ที่ฉันจะอัพโหลดรูปภาพ ฉันจะลองทำทีหลัง

สุขสันต์วันคริสต์มาส.

แก้ไข:

นี่คือภาพบางส่วนของผลลัพธ์สุดท้าย:


1
สวัสดี! ตรวจสอบให้แน่ใจว่าคำตอบของคุณเป็นไปตามข้อกำหนดทั้งหมด: มี 6 ภาพอินพุตและคำตอบควรแสดงผลลัพธ์ของการประมวลผลแต่ละภาพ .
karlphillip

Hi! คุณสามารถส่งผ่านชื่อไฟล์เป็นอาร์กิวเมนต์ CLI ./christmas_tree ./*.pngในการเขียนโปรแกรมของฉัน: พวกเขาสามารถมากเท่าที่คุณต้องการผลลัพธ์จะแสดงให้เห็นอย่างใดอย่างหนึ่งหลังจากที่อื่น ๆ กดปุ่มใด ๆ มันผิดหรือเปล่า?
smeso

ไม่เป็นไร แต่คุณยังต้องอัปโหลดภาพและแชร์ในคำถามของคุณเพื่อให้ผู้ดูของเธรดสามารถเห็นผลลัพธ์ของคุณได้ ให้คนอื่นเห็นว่าคุณทำอะไรบ้างจะช่วยเพิ่มโอกาสในการได้คะแนนโหวต)
karlphillip

ฉันพยายามหาวิธีแก้ปัญหานี้ฉันมีปัญหาการเชื่อมต่อ
smeso

2
ที่ดี! ตอนนี้คุณสามารถ rescale พวกเขาภายในคำตอบด้วยรหัสต่อไปนี้: <img src="http://i.stack.imgur.com/nmzwj.png" width="210" height="150">เพียงแค่เปลี่ยนการเชื่อมโยงไปยังรูปภาพ;)
karlphillip

60

ฉันเขียนโค้ดใน Matlab R2007a ฉันใช้วิธี k เพื่อสกัดต้นคริสต์มาสโดยประมาณ ฉันจะแสดงผลลัพธ์ระดับกลางของฉันด้วยภาพเดียวเท่านั้นและผลลัพธ์สุดท้ายที่มีทั้งหมดหกภาพ

ก่อนอื่นฉันทำการแมปพื้นที่ RGB กับพื้นที่ Lab ซึ่งสามารถเพิ่มความเปรียบต่างของสีแดงในช่อง b:

colorTransform = makecform('srgb2lab');
I = applycform(I, colorTransform);
L = double(I(:,:,1));
a = double(I(:,:,2));
b = double(I(:,:,3));

ป้อนคำอธิบายรูปภาพที่นี่

นอกเหนือจากคุณสมบัติในพื้นที่สีแล้วฉันยังใช้คุณสมบัติพื้นผิวที่เกี่ยวข้องกับพื้นที่ใกล้เคียงมากกว่าแต่ละพิกเซล ที่นี่ฉันรวมความเข้มเชิงเส้นจาก 3 ช่องดั้งเดิม (R, G, B) เข้าด้วยกัน เหตุผลที่ฉันจัดรูปแบบด้วยวิธีนี้ก็เพราะว่าต้นคริสต์มาสในภาพทั้งหมดมีแสงสีแดงติดอยู่และบางครั้งก็เป็นสีเขียว / สีฟ้าบางครั้งก็มีแสงสว่าง

R=double(Irgb(:,:,1));
G=double(Irgb(:,:,2));
B=double(Irgb(:,:,3));
I0 = (3*R + max(G,B)-min(G,B))/2;

ป้อนคำอธิบายรูปภาพที่นี่

ฉันใช้รูปแบบไบนารีท้องถิ่น 3X3 บนI0ใช้พิกเซลกลางเป็นเกณฑ์และได้รับความคมชัดโดยการคำนวณความแตกต่างระหว่างค่าความเข้มของพิกเซลเฉลี่ยเหนือขีด จำกัด และค่าเฉลี่ยต่ำกว่า

I0_copy = zeros(size(I0));
for i = 2 : size(I0,1) - 1
    for j = 2 : size(I0,2) - 1
        tmp = I0(i-1:i+1,j-1:j+1) >= I0(i,j);
        I0_copy(i,j) = mean(mean(tmp.*I0(i-1:i+1,j-1:j+1))) - ...
            mean(mean(~tmp.*I0(i-1:i+1,j-1:j+1))); % Contrast
    end
end

ป้อนคำอธิบายรูปภาพที่นี่

เนื่องจากฉันมีคุณสมบัติทั้งหมด 4 ตัวฉันจะเลือก K = 5 ในวิธีการจัดกลุ่มของฉัน รหัสสำหรับ k-mean แสดงอยู่ด้านล่าง (เป็นหลักสูตรการเรียนรู้จากเครื่องของ Dr. Andrew Ng ฉันเรียนก่อนหน้านี้และฉันเขียนรหัสด้วยตัวเองในการมอบหมายการเขียนโปรแกรมของเขา)

[centroids, idx] = runkMeans(X, initial_centroids, max_iters);
mask=reshape(idx,img_size(1),img_size(2));

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [centroids, idx] = runkMeans(X, initial_centroids, ...
                                  max_iters, plot_progress)
   [m n] = size(X);
   K = size(initial_centroids, 1);
   centroids = initial_centroids;
   previous_centroids = centroids;
   idx = zeros(m, 1);

   for i=1:max_iters    
      % For each example in X, assign it to the closest centroid
      idx = findClosestCentroids(X, centroids);

      % Given the memberships, compute new centroids
      centroids = computeCentroids(X, idx, K);

   end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function idx = findClosestCentroids(X, centroids)
   K = size(centroids, 1);
   idx = zeros(size(X,1), 1);
   for xi = 1:size(X,1)
      x = X(xi, :);
      % Find closest centroid for x.
      best = Inf;
      for mui = 1:K
        mu = centroids(mui, :);
        d = dot(x - mu, x - mu);
        if d < best
           best = d;
           idx(xi) = mui;
        end
      end
   end 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function centroids = computeCentroids(X, idx, K)
   [m n] = size(X);
   centroids = zeros(K, n);
   for mui = 1:K
      centroids(mui, :) = sum(X(idx == mui, :)) / sum(idx == mui);
   end

เนื่องจากโปรแกรมทำงานช้ามากในคอมพิวเตอร์ของฉันฉันเพิ่งรันซ้ำ 3 ครั้ง โดยปกติเกณฑ์หยุดคือ (i) เวลาการวนซ้ำอย่างน้อย 10 ครั้งหรือ (ii) ไม่มีการเปลี่ยนแปลงเซนทรอยด์อีกต่อไป สำหรับการทดสอบของฉันการเพิ่มการวนซ้ำอาจทำให้พื้นหลัง (ท้องฟ้าและต้นไม้ท้องฟ้าและอาคาร ... ) แม่นยำยิ่งขึ้น แต่ไม่ได้แสดงการเปลี่ยนแปลงที่รุนแรงในการสกัดต้นคริสต์มาส นอกจากนี้โปรดสังเกตว่า k-mean ไม่ได้รับผลกระทบจากการกำหนดค่าเริ่มต้นของเซนทรอยด์แบบสุ่มดังนั้นควรรันโปรแกรมหลาย ๆ ครั้งเพื่อทำการเปรียบเทียบ

หลังจาก k-หมายถึงภูมิภาคที่มีป้ายกำกับซึ่งมีความเข้มสูงสุดI0ถูกเลือก และการติดตามขอบเขตถูกใช้เพื่อแยกขอบเขต สำหรับฉันต้นคริสต์มาสที่ผ่านมาเป็นสิ่งที่ยากที่สุดที่จะสกัดเนื่องจากความแตกต่างในภาพนั้นไม่สูงพอเหมือนในห้าต้นแรก อีกประเด็นในวิธีการของฉันคือฉันใช้bwboundariesฟังก์ชันใน Matlab เพื่อติดตามขอบเขต แต่บางครั้งก็มีการรวมขอบเขตภายในไว้ด้วยซึ่งคุณสามารถสังเกตได้ในผลลัพธ์ที่ 3, 5, 6 ด้านมืดภายในต้นคริสต์มาสไม่เพียง แต่ล้มเหลวในการรวมกลุ่มกับด้านสว่าง แต่พวกเขายังนำไปสู่การติดตามขอบเขตภายในเล็ก ๆ จำนวนมาก ( imfillไม่ได้ปรับปรุงมากนัก) ในอัลกอริทึมของฉันทั้งหมดยังคงมีพื้นที่การปรับปรุงมาก

สิ่งพิมพ์บางเล่มระบุว่าค่าเฉลี่ยการเปลี่ยนแปลงอาจแข็งแกร่งกว่าค่าเฉลี่ย k และอัลกอริทึมแบบกราฟที่ตัดหลาย รูปแบบก็มีการแข่งขันสูงในการแบ่งส่วนที่ซับซ้อน ฉันเขียนอัลกอริทึมค่าเฉลี่ยตัวเองดูเหมือนว่าจะแยกพื้นที่โดยไม่มีแสงเพียงพอ แต่การเปลี่ยนค่าเฉลี่ยนั้นค่อนข้างน้อยเกินไปและจำเป็นต้องใช้กลยุทธ์การรวมเข้าด้วยกัน มันวิ่งช้ากว่า k-mean มากในคอมพิวเตอร์ของฉันฉันกลัวว่าฉันจะต้องยอมแพ้ ฉันหวังเป็นอย่างยิ่งว่าจะได้เห็นคนอื่น ๆ จะส่งผลลัพธ์ที่ยอดเยี่ยมที่นี่ด้วยอัลกอริทึมที่ทันสมัยดังกล่าวข้างต้น

แต่ฉันเชื่อเสมอว่าการเลือกคุณสมบัติเป็นองค์ประกอบสำคัญในการแบ่งส่วนภาพ ด้วยการเลือกคุณสมบัติที่เหมาะสมที่สามารถเพิ่มระยะห่างระหว่างวัตถุและพื้นหลังให้ได้สูงสุดอัลกอริทึมการแบ่งส่วนจะทำงานได้อย่างแน่นอน อัลกอริทึมที่ต่างกันอาจปรับปรุงผลลัพธ์จาก 1 เป็น 10 แต่การเลือกคุณสมบัติอาจปรับปรุงจาก 0 เป็น 1

สุขสันต์วันคริสต์มาส !


2
ขอบคุณสำหรับคำตอบ! ฉันแค่อยากจะชี้ให้เห็นว่า Matlab ไม่ใช่โอเพ่นซอร์สแต่Scilabคือ ฉันก็ชอบที่จะเห็นคำตอบนี้แข่งกับคนอื่น ;)
karlphillip

6
ขอบคุณคาร์ล เป็นอีกหนึ่งคู่ซอฟต์แวร์โอเพ่นซอร์สว่าหุ้นเกือบไวยากรณ์การเข้ารหัสเช่นเดียวกันกับ Matlab: mathworks.fr/matlabcentral/answers/14399-gnu-octave-vs-matlab
lennon310

ที่น่าสนใจฉันไม่ทราบว่าขอบคุณ! รหัสของคุณทำงานบนอ็อกเทฟหรือไม่
karlphillip

ฉันยังไม่ได้ทดสอบ แต่ฉันคิดว่ามันไม่มีปัญหา :)
lennon310

@ lennon310 ฉันคิดว่าถ้าคุณวางขอบเขตและรับฮัลล์นูนคุณจะกำจัดปัญหาหลุม จำไว้ว่าตัวเรือนูนนั้นเป็นพื้นที่ที่เล็กที่สุดที่รวมทุกจุดไว้ในชุด
sepdek

57

นี่คือโพสต์สุดท้ายของฉันโดยใช้วิธีการประมวลผลภาพแบบดั้งเดิม ...

นี่ฉันอย่างใดรวมสองข้อเสนออื่น ๆ ของฉันการบรรลุผลลัพธ์ที่ดียิ่งขึ้น ตามความเป็นจริงฉันไม่สามารถเห็นได้ว่าผลลัพธ์เหล่านี้จะดีขึ้นอย่างไร (โดยเฉพาะเมื่อคุณดูที่ภาพที่ถูกหลอกลวงซึ่งวิธีการสร้างขึ้นมา)

หัวใจของวิธีการคือการรวมกันของสมมติฐานหลักสามข้อ :

  1. รูปภาพควรมีความผันผวนสูงในบริเวณต้นไม้
  2. รูปภาพควรมีความเข้มสูงกว่าในบริเวณต้นไม้
  3. ภูมิภาคพื้นหลังควรมีความเข้มต่ำและส่วนใหญ่เป็นสีฟ้า

ด้วยสมมติฐานเหล่านี้ในใจวิธีการทำงานดังต่อไปนี้:

  1. แปลงภาพเป็น HSV
  2. กรองช่อง V ด้วยตัวกรอง LoG
  3. ใช้การจัดรูปแบบใหม่ในภาพที่กรองของ LoG เพื่อรับ 'กิจกรรม' มาส์ก
  4. ใช้ thresholding อย่างหนักกับ V channel เพื่อรับ mask เข้มข้น B
  5. ใช้ thresholding ช่อง H เพื่อจับภาพภูมิภาคสีน้ำเงินเข้มที่มีความเข้มต่ำลงในหน้ากากพื้นหลัง C
  6. รวมมาสก์ที่ใช้ AND เพื่อรับมาสก์สุดท้าย
  7. ขยายหน้ากากเพื่อขยายขอบเขตและเชื่อมต่อพิกเซลที่กระจายออกไป
  8. กำจัดพื้นที่เล็ก ๆ และรับหน้ากากขั้นสุดท้ายซึ่งในที่สุดจะแสดงเฉพาะต้นไม้

นี่คือรหัสใน MATLAB (อีกครั้งสคริปต์โหลดรูปภาพ jpg ทั้งหมดในโฟลเดอร์ปัจจุบันและอีกครั้งรหัสนี้ไม่ได้เป็นส่วนของรหัสที่ได้รับการปรับปรุง)

% clear everything
clear;
pack;
close all;
close all hidden;
drawnow;
clc;

% initialization
ims=dir('./*.jpg');
imgs={};
images={}; 
blur_images={}; 
log_image={}; 
dilated_image={};
int_image={};
back_image={};
bin_image={};
measurements={};
box={};
num=length(ims);
thres_div = 3;

for i=1:num, 
    % load original image
    imgs{end+1}=imread(ims(i).name);

    % convert to HSV colorspace
    images{end+1}=rgb2hsv(imgs{i});

    % apply laplacian filtering and heuristic hard thresholding
    val_thres = (max(max(images{i}(:,:,3)))/thres_div);
    log_image{end+1} = imfilter( images{i}(:,:,3),fspecial('log')) > val_thres;

    % get the most bright regions of the image
    int_thres = 0.26*max(max( images{i}(:,:,3)));
    int_image{end+1} = images{i}(:,:,3) > int_thres;

    % get the most probable background regions of the image
    back_image{end+1} = images{i}(:,:,1)>(150/360) & images{i}(:,:,1)<(320/360) & images{i}(:,:,3)<0.5;

    % compute the final binary image by combining 
    % high 'activity' with high intensity
    bin_image{end+1} = logical( log_image{i}) & logical( int_image{i}) & ~logical( back_image{i});

    % apply morphological dilation to connect distonnected components
    strel_size = round(0.01*max(size(imgs{i})));        % structuring element for morphological dilation
    dilated_image{end+1} = imdilate( bin_image{i}, strel('disk',strel_size));

    % do some measurements to eliminate small objects
    measurements{i} = regionprops( logical( dilated_image{i}),'Area','BoundingBox');

    % iterative enlargement of the structuring element for better connectivity
    while length(measurements{i})>14 && strel_size<(min(size(imgs{i}(:,:,1)))/2),
        strel_size = round( 1.5 * strel_size);
        dilated_image{i} = imdilate( bin_image{i}, strel('disk',strel_size));
        measurements{i} = regionprops( logical( dilated_image{i}),'Area','BoundingBox');
    end

    for m=1:length(measurements{i})
        if measurements{i}(m).Area < 0.05*numel( dilated_image{i})
            dilated_image{i}( round(measurements{i}(m).BoundingBox(2):measurements{i}(m).BoundingBox(4)+measurements{i}(m).BoundingBox(2)),...
                round(measurements{i}(m).BoundingBox(1):measurements{i}(m).BoundingBox(3)+measurements{i}(m).BoundingBox(1))) = 0;
        end
    end
    % make sure the dilated image is the same size with the original
    dilated_image{i} = dilated_image{i}(1:size(imgs{i},1),1:size(imgs{i},2));
    % compute the bounding box
    [y,x] = find( dilated_image{i});
    if isempty( y)
        box{end+1}=[];
    else
        box{end+1} = [ min(x) min(y) max(x)-min(x)+1 max(y)-min(y)+1];
    end
end 

%%% additional code to display things
for i=1:num,
    figure;
    subplot(121);
    colormap gray;
    imshow( imgs{i});
    if ~isempty(box{i})
        hold on;
        rr = rectangle( 'position', box{i});
        set( rr, 'EdgeColor', 'r');
        hold off;
    end
    subplot(122);
    imshow( imgs{i}.*uint8(repmat(dilated_image{i},[1 1 3])));
end

ผล

ผล

ยังมีผลลัพธ์ความละเอียดสูงที่นี่!
การทดลองเพิ่มเติมที่มีรูปภาพเพิ่มเติมสามารถพบได้ที่นี่


1
สิ่งที่ยอดเยี่ยม! โปรดตรวจสอบให้แน่ใจว่าคำตอบอื่น ๆ ของคุณเป็นไปตามรูปแบบนี้ เพื่อชิงเงินรางวัลคุณต้องใช้เทคโนโลยีโอเพ่นซอร์สและน่าเสียดายที่ Matlab ไม่ใช่หนึ่งในนั้น อย่างไรก็ตาม SciLab และ Octave มีและมีไวยากรณ์และฟังก์ชั่นที่คล้ายกัน ;)
karlphillip

รหัสระดับแปดเสียงจะเหมือนกัน ...
sepdek

@karlphillip คำถามนี้ท้ายที่สุดก็มีแท็ก Matlab ถ้าโอเพ่นซอร์สนั้นเป็นสิ่งที่ฉันต้องแนะนำจริงๆ
Dennis Jaheruddin

@sepdek ดีมากอาจจะมีบางสิ่งที่ยังสามารถทำได้เพื่อรวม 'หลุม' ในภาพสุดท้าย (เพิ่มพิกเซลทั้งหมดที่ล้อมรอบด้วยพิกเซลที่ได้รับอนุมัติอย่างสมบูรณ์หรือไม่!)
Dennis Jaheruddin

1
@karlphillip ขอบคุณมาก! ฉันดีใจที่คุณพบว่าวิธีการของฉันน่าสนใจ นอกจากนี้ฉันขอแสดงความยินดีกับคุณในการเลือกโซลูชันที่หรูหราที่สุดและไม่ใช่โซลูชันที่ได้คะแนนมากที่สุด !!!
sepdek

36

ขั้นตอนการแก้ปัญหาของฉัน:

  1. รับช่อง R (จาก RGB) - การทำงานทั้งหมดที่เราทำในช่องนี้:

  2. สร้างภูมิภาคที่น่าสนใจ (ROI)

    • Threshold R channel ที่มีค่า min 149 (ภาพบนขวา)

    • ขยายขอบเขตผลลัพธ์ (ภาพกลางซ้าย)

  3. ตรวจจับชายคาในคำนวณ ROI ต้นไม้มีขอบจำนวนมาก (ภาพขวากลาง)

    • ขยายผลลัพธ์

    • เซาะด้วยรัศมีที่ใหญ่กว่า (ภาพล่างซ้าย)

  4. เลือกวัตถุที่ใหญ่ที่สุด (ตามพื้นที่) - เป็นพื้นที่ผลลัพธ์

  5. ConvexHull (ต้นไม้เป็นรูปหลายเหลี่ยมนูน) (ภาพล่างขวา)

  6. กล่องกระโดด (ภาพด้านล่างขวา - กล่องสีเทา)

เป็นขั้นเป็นตอน: ป้อนคำอธิบายรูปภาพที่นี่

ผลลัพธ์แรก - ซอฟต์แวร์ที่ง่ายที่สุด แต่ไม่ได้อยู่ในซอฟต์แวร์โอเพนซอร์ซ - "Adaptive Vision Studio + Adaptive Vision Library": นี่ไม่ใช่โอเพ่นซอร์ส แต่เป็นต้นแบบอย่างรวดเร็วจริง ๆ :

อัลกอริทึมทั้งหมดในการตรวจสอบต้นคริสต์มาส (11 ช่วงตึก): โซลูชัน AVL

ขั้นตอนต่อไป. เราต้องการโซลูชันโอเพ่นซอร์ส เปลี่ยนตัวกรอง AVL เป็นตัวกรอง OpenCV: ที่นี่ฉันได้ทำการเปลี่ยนแปลงเล็กน้อยเช่นการตรวจจับขอบใช้ตัวกรอง cvCanny เพื่อเคารพฉันได้คูณภาพภูมิภาคด้วยภาพขอบเพื่อเลือกองค์ประกอบที่ใหญ่ที่สุดที่ฉันใช้ findContours + contourArea แต่แนวคิดก็เหมือนกัน

https://www.youtube.com/watch?v=sfjB3MigLH0&index=1&list=UUpSRrkMHNHiLDXgylwhWNQQ

โซลูชั่น OpenCV

ตอนนี้ฉันไม่สามารถแสดงรูปภาพที่มีขั้นตอนกลางได้เนื่องจากฉันสามารถใส่ได้เพียง 2 ลิงก์

ตกลงตอนนี้เราใช้ตัวกรอง openSource แต่ยังไม่เปิดแหล่งที่มาทั้งหมด ขั้นตอนสุดท้าย - พอร์ตถึงรหัส c ++ ฉันใช้ OpenCV ในเวอร์ชัน 2.4.4

ผลลัพธ์ของรหัส c ++ สุดท้ายคือ: ป้อนคำอธิบายรูปภาพที่นี่

รหัส c ++ นั้นค่อนข้างสั้น:

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
#include <algorithm>
using namespace cv;

int main()
{

    string images[6] = {"..\\1.png","..\\2.png","..\\3.png","..\\4.png","..\\5.png","..\\6.png"};

    for(int i = 0; i < 6; ++i)
    {
        Mat img, thresholded, tdilated, tmp, tmp1;
        vector<Mat> channels(3);

        img = imread(images[i]);
        split(img, channels);
        threshold( channels[2], thresholded, 149, 255, THRESH_BINARY);                      //prepare ROI - threshold
        dilate( thresholded, tdilated,  getStructuringElement( MORPH_RECT, Size(22,22) ) ); //prepare ROI - dilate
        Canny( channels[2], tmp, 75, 125, 3, true );    //Canny edge detection
        multiply( tmp, tdilated, tmp1 );    // set ROI

        dilate( tmp1, tmp, getStructuringElement( MORPH_RECT, Size(20,16) ) ); // dilate
        erode( tmp, tmp1, getStructuringElement( MORPH_RECT, Size(36,36) ) ); // erode

        vector<vector<Point> > contours, contours1(1);
        vector<Point> convex;
        vector<Vec4i> hierarchy;
        findContours( tmp1, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

        //get element of maximum area
        //int bestID = std::max_element( contours.begin(), contours.end(), 
        //  []( const vector<Point>& A, const vector<Point>& B ) { return contourArea(A) < contourArea(B); } ) - contours.begin();

            int bestID = 0;
        int bestArea = contourArea( contours[0] );
        for( int i = 1; i < contours.size(); ++i )
        {
            int area = contourArea( contours[i] );
            if( area > bestArea )
            {
                bestArea  = area;
                bestID = i;
            }
        }

        convexHull( contours[bestID], contours1[0] ); 
        drawContours( img, contours1, 0, Scalar( 100, 100, 255 ), img.rows / 100, 8, hierarchy, 0, Point() );

        imshow("image", img );
        waitKey(0);
    }


    return 0;
}

คอมไพเลอร์ใดที่สามารถสร้างโปรแกรมนี้โดยไม่มีข้อผิดพลาด?
karlphillip

ฉันใช้ Visual Studio 2012 เพื่อสร้าง คุณควรใช้คอมไพเลอร์ c ++ พร้อมรองรับ c ++ 11
AdamF

ฉันไม่มีระบบในการจัดการกับสิ่งนั้น คุณช่วยเขียนstd::max_element()สายใหม่ได้ไหม? ฉันต้องการให้รางวัลคำตอบของคุณเช่นกัน ฉันคิดว่าฉันมี gcc 4.2
karlphillip

ตกลงนี่คือคุณสมบัติ c ++ 11;) ฉันเปลี่ยนรหัสต้นฉบับด้านบน กรุณาลองตอนนี้
AdamF

เอาล่ะขอบคุณ ฉันทดสอบมันและมันก็สวยงาม ทันทีที่คำถามนี้เปิดขึ้นอีกครั้ง (ผู้ใช้รายอื่นต้องช่วยฉันด้วย) ฉันสามารถตั้งค่ารางวัลอื่นให้รางวัลคุณได้ ขอแสดงความยินดี!
karlphillip

31

... อีกวิธีแก้ปัญหาแบบเก่า - ขึ้นอยู่กับการประมวลผล HSV :

  1. แปลงภาพให้เป็นสี HSV
  2. สร้างมาสก์ตามการวิเคราะห์พฤติกรรมใน HSV (ดูด้านล่าง)
  3. ใช้การขยายทางสัณฐานวิทยากับหน้ากากเพื่อเชื่อมต่อบริเวณที่ไม่ได้เชื่อมต่อ
  4. ทิ้งพื้นที่ขนาดเล็กและบล็อกแนวนอน (จำไว้ว่าต้นไม้เป็นบล็อกแนวตั้ง)
  5. คำนวณกล่องขอบเขต

คำศัพท์เกี่ยวกับการวิเคราะห์พฤติกรรมในการประมวลผล HSV:

  1. ทุกอย่างที่มีเฉดสี (H) อยู่ระหว่าง 210 - 320 องศาจะถูกยกเลิกเป็นสีน้ำเงินม่วงที่ควรจะอยู่ในพื้นหลังหรือในพื้นที่ที่ไม่เกี่ยวข้อง
  2. ทุกอย่างที่มีค่า (V) ต่ำกว่านั้น 40%ก็ถูกทิ้งว่ามืดเกินไปที่จะเกี่ยวข้อง

แน่นอนว่าหนึ่งอาจทดลองกับความเป็นไปได้อื่น ๆ อีกมากมายในการปรับแต่งวิธีนี้ ...

นี่คือรหัส MATLAB ที่จะทำเคล็ดลับ (คำเตือน: รหัสอยู่ไกลจากการเพิ่มประสิทธิภาพ !!! ฉันใช้เทคนิคที่ไม่แนะนำสำหรับการเขียนโปรแกรม MATLAB เพียงเพื่อให้สามารถติดตามสิ่งต่าง ๆ ในกระบวนการนี้สามารถเพิ่มประสิทธิภาพอย่างมาก):

% clear everything
clear;
pack;
close all;
close all hidden;
drawnow;
clc;

% initialization
ims=dir('./*.jpg');
num=length(ims);

imgs={};
hsvs={}; 
masks={};
dilated_images={};
measurements={};
boxs={};

for i=1:num, 
    % load original image
    imgs{end+1} = imread(ims(i).name);
    flt_x_size = round(size(imgs{i},2)*0.005);
    flt_y_size = round(size(imgs{i},1)*0.005);
    flt = fspecial( 'average', max( flt_y_size, flt_x_size));
    imgs{i} = imfilter( imgs{i}, flt, 'same');
    % convert to HSV colorspace
    hsvs{end+1} = rgb2hsv(imgs{i});
    % apply a hard thresholding and binary operation to construct the mask
    masks{end+1} = medfilt2( ~(hsvs{i}(:,:,1)>(210/360) & hsvs{i}(:,:,1)<(320/360))&hsvs{i}(:,:,3)>0.4);
    % apply morphological dilation to connect distonnected components
    strel_size = round(0.03*max(size(imgs{i})));        % structuring element for morphological dilation
    dilated_images{end+1} = imdilate( masks{i}, strel('disk',strel_size));
    % do some measurements to eliminate small objects
    measurements{i} = regionprops( dilated_images{i},'Perimeter','Area','BoundingBox'); 
    for m=1:length(measurements{i})
        if (measurements{i}(m).Area < 0.02*numel( dilated_images{i})) || (measurements{i}(m).BoundingBox(3)>1.2*measurements{i}(m).BoundingBox(4))
            dilated_images{i}( round(measurements{i}(m).BoundingBox(2):measurements{i}(m).BoundingBox(4)+measurements{i}(m).BoundingBox(2)),...
                round(measurements{i}(m).BoundingBox(1):measurements{i}(m).BoundingBox(3)+measurements{i}(m).BoundingBox(1))) = 0;
        end
    end
    dilated_images{i} = dilated_images{i}(1:size(imgs{i},1),1:size(imgs{i},2));
    % compute the bounding box
    [y,x] = find( dilated_images{i});
    if isempty( y)
        boxs{end+1}=[];
    else
        boxs{end+1} = [ min(x) min(y) max(x)-min(x)+1 max(y)-min(y)+1];
    end

end 

%%% additional code to display things
for i=1:num,
    figure;
    subplot(121);
    colormap gray;
    imshow( imgs{i});
    if ~isempty(boxs{i})
        hold on;
        rr = rectangle( 'position', boxs{i});
        set( rr, 'EdgeColor', 'r');
        hold off;
    end
    subplot(122);
    imshow( imgs{i}.*uint8(repmat(dilated_images{i},[1 1 3])));
end

ผล:

ในผลลัพธ์ฉันแสดงรูปภาพที่ถูกปิดบังและกล่องขอบเขต ป้อนคำอธิบายรูปภาพที่นี่


สวัสดีขอบคุณสำหรับคำตอบ โปรดสละเวลาสักครู่เพื่ออ่านหัวข้อข้อกำหนดเพื่อให้แน่ใจว่าคำตอบของคุณเป็นไปตามคำแนะนำทั้งหมด คุณลืมที่จะแบ่งปันภาพที่เกิดขึ้น ;)
karlphillip

2
@karlphillip sepdek มีชื่อเสียงไม่มากพอที่จะแบ่งปันรูปภาพฉันย้ายรูปภาพไปที่ส่วนของคำตอบตามลิงก์และคำแนะนำของเขา ไม่แน่ใจว่าสิ่งเหล่านั้นถูกต้องหรือไม่โปรดแสดงความคิดเห็นในส่วนนี้
alko

@alko ฉันรู้ขอบคุณ แต่บางส่วนของภาพที่คุณใช้ร่วมกันไม่ได้อยู่ในชุดการป้อนข้อมูล คำตอบจะต้องแสดงผลลัพธ์ของการประมวลผลทั้งหมด 6 ภาพที่แชร์กับคำถาม
karlphillip

@karlphillip นั่นคือภาพของเขาไม่ใช่ของฉัน นั่นคือสิ่งที่ฉันหมายถึงโดย "แสดงความคิดเห็นในส่วนนี้";)
alko

2
ขออภัยที่ทำให้เกิดปัญหา ... ไม่ใช่ความตั้งใจของฉัน ฉันได้รวมรูปภาพทั้งหมดในชุดข้อมูลเริ่มต้นและปรับปรุงด้วยยิ่งขึ้นเพียงเพื่อพิสูจน์ว่าแนวคิดของฉันแข็งแกร่ง ...
sepdek

23

วิธีการประมวลผลภาพแบบเก่า ...
แนวคิดนี้มีพื้นฐานมาจากการสันนิษฐานว่ารูปภาพแสดงต้นไม้ที่มีแสงบนพื้นหลังที่มืดและเรียบกว่าปกติ (หรือฉากหน้าในบางกรณี) พื้นที่ต้นไม้สว่างมาก "พลัง" และมีความเข้มสูง
กระบวนการดังต่อไปนี้:

  1. แปลงเป็นระดับสีเทา
  2. ใช้การกรอง LoG เพื่อรับพื้นที่ "ใช้งาน" มากที่สุด
  3. ใช้การนวดด้วยความร้อนสูงเพื่อให้ได้พื้นที่ที่สว่างที่สุด
  4. รวม 2 ก่อนหน้านี้เพื่อรับมาสก์เบื้องต้น
  5. ใช้การขยายทางสัณฐานวิทยาเพื่อขยายพื้นที่และเชื่อมต่อส่วนประกอบใกล้เคียง
  6. กำจัดพื้นที่ตัวเลือกขนาดเล็กตามขนาดพื้นที่ของพวกเขา

สิ่งที่คุณจะได้รับคือหน้ากากไบนารีและกรอบสำหรับแต่ละภาพ

นี่คือผลลัพธ์ที่ใช้เทคนิคไร้เดียงสานี้: ป้อนคำอธิบายรูปภาพที่นี่

รหัสบน MATLAB ดังนี้: รหัสทำงานบนโฟลเดอร์ที่มีภาพ JPG โหลดภาพทั้งหมดและส่งกลับผลลัพธ์ที่ตรวจพบ

% clear everything
clear;
pack;
close all;
close all hidden;
drawnow;
clc;

% initialization
ims=dir('./*.jpg');
imgs={};
images={}; 
blur_images={}; 
log_image={}; 
dilated_image={};
int_image={};
bin_image={};
measurements={};
box={};
num=length(ims);
thres_div = 3;

for i=1:num, 
    % load original image
    imgs{end+1}=imread(ims(i).name);

    % convert to grayscale
    images{end+1}=rgb2gray(imgs{i});

    % apply laplacian filtering and heuristic hard thresholding
    val_thres = (max(max(images{i}))/thres_div);
    log_image{end+1} = imfilter( images{i},fspecial('log')) > val_thres;

    % get the most bright regions of the image
    int_thres = 0.26*max(max( images{i}));
    int_image{end+1} = images{i} > int_thres;

    % compute the final binary image by combining 
    % high 'activity' with high intensity
    bin_image{end+1} = log_image{i} .* int_image{i};

    % apply morphological dilation to connect distonnected components
    strel_size = round(0.01*max(size(imgs{i})));        % structuring element for morphological dilation
    dilated_image{end+1} = imdilate( bin_image{i}, strel('disk',strel_size));

    % do some measurements to eliminate small objects
    measurements{i} = regionprops( logical( dilated_image{i}),'Area','BoundingBox');
    for m=1:length(measurements{i})
        if measurements{i}(m).Area < 0.05*numel( dilated_image{i})
            dilated_image{i}( round(measurements{i}(m).BoundingBox(2):measurements{i}(m).BoundingBox(4)+measurements{i}(m).BoundingBox(2)),...
                round(measurements{i}(m).BoundingBox(1):measurements{i}(m).BoundingBox(3)+measurements{i}(m).BoundingBox(1))) = 0;
        end
    end
    % make sure the dilated image is the same size with the original
    dilated_image{i} = dilated_image{i}(1:size(imgs{i},1),1:size(imgs{i},2));
    % compute the bounding box
    [y,x] = find( dilated_image{i});
    if isempty( y)
        box{end+1}=[];
    else
        box{end+1} = [ min(x) min(y) max(x)-min(x)+1 max(y)-min(y)+1];
    end
end 

%%% additional code to display things
for i=1:num,
    figure;
    subplot(121);
    colormap gray;
    imshow( imgs{i});
    if ~isempty(box{i})
        hold on;
        rr = rectangle( 'position', box{i});
        set( rr, 'EdgeColor', 'r');
        hold off;
    end
    subplot(122);
    imshow( imgs{i}.*uint8(repmat(dilated_image{i},[1 1 3])));
end

อย่าลืมอัปโหลดภาพผลลัพธ์เช่นเดียวกับ Faust
karlphillip

ฉันเป็น noob ที่นี่ดังนั้นฉันจึงไม่สามารถอัพโหลดภาพได้ โปรดดูผลลัพธ์ในลิงก์ที่ให้ไว้ในคำอธิบายของฉัน
sepdek

ตกลง แต่คุณยังต้องใช้ภาพที่แชร์กับคำถามเหมือนคนอื่น ๆ กำลังทำอยู่ เมื่อคุณประมวลผลให้อัปโหลดที่ใดก็ได้และแก้ไขคำตอบเพื่อเพิ่มลิงค์ หลังจากนั้นฉันจะแก้ไขคำตอบของคุณและวางภาพไว้ข้างในให้คุณ
karlphillip

ลิงค์ปรากฏขึ้นเพื่อให้มีภาพที่ถูกต้องในขณะนี้
Dennis Jaheruddin

22

ด้วยวิธีการที่แตกต่างจากสิ่งที่ฉันเห็นฉันสร้าง สคริปต์ที่ตรวจพบต้นคริสต์มาสด้วยแสงไฟ ผลลัพธ์จะเป็นรูปสามเหลี่ยมสมมาตรเสมอและหากจำเป็นต้องใช้ค่าตัวเลขเช่นมุม ("ความอ้วน") ของต้นไม้

ภัยคุกคามที่ใหญ่ที่สุดของอัลกอริธึมนี้คือไฟที่อยู่ถัดจาก (เป็นจำนวนมาก) หรืออยู่ด้านหน้าของต้นไม้ แก้ไข (เพิ่มเติม): สิ่งที่ทำไม่ได้: ตรวจสอบว่ามีต้นคริสต์มาสหรือไม่ค้นหาต้นคริสต์มาสหลายต้นในภาพเดียวตรวจพบต้นคริสต์มาสในกลางลาสเวกัสตรวจพบต้นคริสต์มาสที่โค้งงออย่างหนัก คว่ำหรือสับลง ... ;)

ขั้นตอนต่าง ๆ คือ:

  • คำนวณความสว่างที่เพิ่มขึ้น (R + G + B) สำหรับแต่ละพิกเซล
  • เพิ่มค่านี้ของพิกเซลข้างเคียงทั้งหมด 8 ตัวที่ด้านบนของแต่ละพิกเซล
  • จัดอันดับพิกเซลทั้งหมดด้วยค่านี้ (สว่างที่สุดก่อน) - ฉันรู้ว่าไม่บอบบางจริงๆ ...
  • เลือก N ของสิ่งเหล่านี้เริ่มต้นจากด้านบนข้ามสิ่งที่อยู่ใกล้เกินไป
  • คำนวณ ด้านบน N เหล่านี้ (ทำให้เรามีศูนย์กลางโดยประมาณของต้นไม้)
  • เริ่มต้นจากตำแหน่งค่ามัธยฐานขึ้นไปในลำแสงค้นหาที่กว้างขึ้นสำหรับแสงสูงสุดจากแสงที่เลือก (ผู้คนมักจะวางแสงอย่างน้อยหนึ่งที่ด้านบนสุด)
  • จากนั้นลองจินตนาการถึงเส้นที่เลื่อนไปทางซ้ายและขวา 60 องศา (ต้นคริสต์มาสไม่ควรอ้วนขนาดนั้น)
  • ลดค่าเหล่านั้น 60 องศาจนกระทั่ง 20% ของแสงที่สว่างที่สุดอยู่นอกสามเหลี่ยมนี้
  • ค้นหาแสงที่ด้านล่างสุดของสามเหลี่ยมโดยให้ขอบแนวนอนด้านล่างของต้นไม้แก่คุณ
  • เสร็จสิ้น

คำอธิบายของเครื่องหมาย:

  • กากบาทสีแดงขนาดใหญ่อยู่กลางต้นไม้: ค่ามัธยฐานของแสง N ที่สว่างที่สุดด้านบน
  • เส้นประจากที่นั่นขึ้นไป: "ลำแสงค้นหา" สำหรับด้านบนของต้นไม้
  • กากบาทสีแดงขนาดเล็ก: ด้านบนของต้นไม้
  • กากบาทสีแดงเล็ก ๆ จริง ๆ : ไฟ N ที่สว่างที่สุดทั้งหมดด้านบน
  • สามเหลี่ยมสีแดง: D'uh!

รหัสแหล่งที่มา:

<?php

ini_set('memory_limit', '1024M');

header("Content-type: image/png");

$chosenImage = 6;

switch($chosenImage){
    case 1:
        $inputImage     = imagecreatefromjpeg("nmzwj.jpg");
        break;
    case 2:
        $inputImage     = imagecreatefromjpeg("2y4o5.jpg");
        break;
    case 3:
        $inputImage     = imagecreatefromjpeg("YowlH.jpg");
        break;
    case 4:
        $inputImage     = imagecreatefromjpeg("2K9Ef.jpg");
        break;
    case 5:
        $inputImage     = imagecreatefromjpeg("aVZhC.jpg");
        break;
    case 6:
        $inputImage     = imagecreatefromjpeg("FWhSP.jpg");
        break;
    case 7:
        $inputImage     = imagecreatefromjpeg("roemerberg.jpg");
        break;
    default:
        exit();
}

// Process the loaded image

$topNspots = processImage($inputImage);

imagejpeg($inputImage);
imagedestroy($inputImage);

// Here be functions

function processImage($image) {
    $orange = imagecolorallocate($image, 220, 210, 60);
    $black = imagecolorallocate($image, 0, 0, 0);
    $red = imagecolorallocate($image, 255, 0, 0);

    $maxX = imagesx($image)-1;
    $maxY = imagesy($image)-1;

    // Parameters
    $spread = 1; // Number of pixels to each direction that will be added up
    $topPositions = 80; // Number of (brightest) lights taken into account
    $minLightDistance = round(min(array($maxX, $maxY)) / 30); // Minimum number of pixels between the brigtests lights
    $searchYperX = 5; // spread of the "search beam" from the median point to the top

    $renderStage = 3; // 1 to 3; exits the process early


    // STAGE 1
    // Calculate the brightness of each pixel (R+G+B)

    $maxBrightness = 0;
    $stage1array = array();

    for($row = 0; $row <= $maxY; $row++) {

        $stage1array[$row] = array();

        for($col = 0; $col <= $maxX; $col++) {

            $rgb = imagecolorat($image, $col, $row);
            $brightness = getBrightnessFromRgb($rgb);
            $stage1array[$row][$col] = $brightness;

            if($renderStage == 1){
                $brightnessToGrey = round($brightness / 765 * 256);
                $greyRgb = imagecolorallocate($image, $brightnessToGrey, $brightnessToGrey, $brightnessToGrey);
                imagesetpixel($image, $col, $row, $greyRgb);
            }

            if($brightness > $maxBrightness) {
                $maxBrightness = $brightness;
                if($renderStage == 1){
                    imagesetpixel($image, $col, $row, $red);
                }
            }
        }
    }
    if($renderStage == 1) {
        return;
    }


    // STAGE 2
    // Add up brightness of neighbouring pixels

    $stage2array = array();
    $maxStage2 = 0;

    for($row = 0; $row <= $maxY; $row++) {
        $stage2array[$row] = array();

        for($col = 0; $col <= $maxX; $col++) {
            if(!isset($stage2array[$row][$col])) $stage2array[$row][$col] = 0;

            // Look around the current pixel, add brightness
            for($y = $row-$spread; $y <= $row+$spread; $y++) {
                for($x = $col-$spread; $x <= $col+$spread; $x++) {

                    // Don't read values from outside the image
                    if($x >= 0 && $x <= $maxX && $y >= 0 && $y <= $maxY){
                        $stage2array[$row][$col] += $stage1array[$y][$x]+10;
                    }
                }
            }

            $stage2value = $stage2array[$row][$col];
            if($stage2value > $maxStage2) {
                $maxStage2 = $stage2value;
            }
        }
    }

    if($renderStage >= 2){
        // Paint the accumulated light, dimmed by the maximum value from stage 2
        for($row = 0; $row <= $maxY; $row++) {
            for($col = 0; $col <= $maxX; $col++) {
                $brightness = round($stage2array[$row][$col] / $maxStage2 * 255);
                $greyRgb = imagecolorallocate($image, $brightness, $brightness, $brightness);
                imagesetpixel($image, $col, $row, $greyRgb);
            }
        }
    }

    if($renderStage == 2) {
        return;
    }


    // STAGE 3

    // Create a ranking of bright spots (like "Top 20")
    $topN = array();

    for($row = 0; $row <= $maxY; $row++) {
        for($col = 0; $col <= $maxX; $col++) {

            $stage2Brightness = $stage2array[$row][$col];
            $topN[$col.":".$row] = $stage2Brightness;
        }
    }
    arsort($topN);

    $topNused = array();
    $topPositionCountdown = $topPositions;

    if($renderStage == 3){
        foreach ($topN as $key => $val) {
            if($topPositionCountdown <= 0){
                break;
            }

            $position = explode(":", $key);

            foreach($topNused as $usedPosition => $usedValue) {
                $usedPosition = explode(":", $usedPosition);
                $distance = abs($usedPosition[0] - $position[0]) + abs($usedPosition[1] - $position[1]);
                if($distance < $minLightDistance) {
                    continue 2;
                }
            }

            $topNused[$key] = $val;

            paintCrosshair($image, $position[0], $position[1], $red, 2);

            $topPositionCountdown--;

        }
    }


    // STAGE 4
    // Median of all Top N lights
    $topNxValues = array();
    $topNyValues = array();

    foreach ($topNused as $key => $val) {
        $position = explode(":", $key);
        array_push($topNxValues, $position[0]);
        array_push($topNyValues, $position[1]);
    }

    $medianXvalue = round(calculate_median($topNxValues));
    $medianYvalue = round(calculate_median($topNyValues));
    paintCrosshair($image, $medianXvalue, $medianYvalue, $red, 15);


    // STAGE 5
    // Find treetop

    $filename = 'debug.log';
    $handle = fopen($filename, "w");
    fwrite($handle, "\n\n STAGE 5");

    $treetopX = $medianXvalue;
    $treetopY = $medianYvalue;

    $searchXmin = $medianXvalue;
    $searchXmax = $medianXvalue;

    $width = 0;
    for($y = $medianYvalue; $y >= 0; $y--) {
        fwrite($handle, "\nAt y = ".$y);

        if(($y % $searchYperX) == 0) { // Modulo
            $width++;
            $searchXmin = $medianXvalue - $width;
            $searchXmax = $medianXvalue + $width;
            imagesetpixel($image, $searchXmin, $y, $red);
            imagesetpixel($image, $searchXmax, $y, $red);
        }

        foreach ($topNused as $key => $val) {
            $position = explode(":", $key); // "x:y"

            if($position[1] != $y){
                continue;
            }

            if($position[0] >= $searchXmin && $position[0] <= $searchXmax){
                $treetopX = $position[0];
                $treetopY = $y;
            }
        }

    }

    paintCrosshair($image, $treetopX, $treetopY, $red, 5);


    // STAGE 6
    // Find tree sides
    fwrite($handle, "\n\n STAGE 6");

    $treesideAngle = 60; // The extremely "fat" end of a christmas tree
    $treeBottomY = $treetopY;

    $topPositionsExcluded = 0;
    $xymultiplier = 0;
    while(($topPositionsExcluded < ($topPositions / 5)) && $treesideAngle >= 1){
        fwrite($handle, "\n\nWe're at angle ".$treesideAngle);
        $xymultiplier = sin(deg2rad($treesideAngle));
        fwrite($handle, "\nMultiplier: ".$xymultiplier);

        $topPositionsExcluded = 0;
        foreach ($topNused as $key => $val) {
            $position = explode(":", $key);
            fwrite($handle, "\nAt position ".$key);

            if($position[1] > $treeBottomY) {
                $treeBottomY = $position[1];
            }

            // Lights above the tree are outside of it, but don't matter
            if($position[1] < $treetopY){
                $topPositionsExcluded++;
                fwrite($handle, "\nTOO HIGH");
                continue;
            }

            // Top light will generate division by zero
            if($treetopY-$position[1] == 0) {
                fwrite($handle, "\nDIVISION BY ZERO");
                continue;
            }

            // Lights left end right of it are also not inside
            fwrite($handle, "\nLight position factor: ".(abs($treetopX-$position[0]) / abs($treetopY-$position[1])));
            if((abs($treetopX-$position[0]) / abs($treetopY-$position[1])) > $xymultiplier){
                $topPositionsExcluded++;
                fwrite($handle, "\n --- Outside tree ---");
            }
        }

        $treesideAngle--;
    }
    fclose($handle);

    // Paint tree's outline
    $treeHeight = abs($treetopY-$treeBottomY);
    $treeBottomLeft = 0;
    $treeBottomRight = 0;
    $previousState = false; // line has not started; assumes the tree does not "leave"^^

    for($x = 0; $x <= $maxX; $x++){
        if(abs($treetopX-$x) != 0 && abs($treetopX-$x) / $treeHeight > $xymultiplier){
            if($previousState == true){
                $treeBottomRight = $x;
                $previousState = false;
            }
            continue;
        }
        imagesetpixel($image, $x, $treeBottomY, $red);
        if($previousState == false){
            $treeBottomLeft = $x;
            $previousState = true;
        }
    }
    imageline($image, $treeBottomLeft, $treeBottomY, $treetopX, $treetopY, $red);
    imageline($image, $treeBottomRight, $treeBottomY, $treetopX, $treetopY, $red);


    // Print out some parameters

    $string = "Min dist: ".$minLightDistance." | Tree angle: ".$treesideAngle." deg | Tree bottom: ".$treeBottomY;

    $px     = (imagesx($image) - 6.5 * strlen($string)) / 2;
    imagestring($image, 2, $px, 5, $string, $orange);

    return $topN;
}

/**
 * Returns values from 0 to 765
 */
function getBrightnessFromRgb($rgb) {
    $r = ($rgb >> 16) & 0xFF;
    $g = ($rgb >> 8) & 0xFF;
    $b = $rgb & 0xFF;

    return $r+$r+$b;
}

function paintCrosshair($image, $posX, $posY, $color, $size=5) {
    for($x = $posX-$size; $x <= $posX+$size; $x++) {
        if($x>=0 && $x < imagesx($image)){
            imagesetpixel($image, $x, $posY, $color);
        }
    }
    for($y = $posY-$size; $y <= $posY+$size; $y++) {
        if($y>=0 && $y < imagesy($image)){
            imagesetpixel($image, $posX, $y, $color);
        }
    }
}

// From http://www.mdj.us/web-development/php-programming/calculating-the-median-average-values-of-an-array-with-php/
function calculate_median($arr) {
    sort($arr);
    $count = count($arr); //total numbers in array
    $middleval = floor(($count-1)/2); // find the middle value, or the lowest middle value
    if($count % 2) { // odd number, middle is the median
        $median = $arr[$middleval];
    } else { // even number, calculate avg of 2 medians
        $low = $arr[$middleval];
        $high = $arr[$middleval+1];
        $median = (($low+$high)/2);
    }
    return $median;
}


?>

รูปภาพ: บนด้านซ้าย กลางส่วนล่าง ด้านล่างซ้าย บนขวา กลางตอนบน ด้านล่างขวา

โบนัส: Weihnachtsbaum เยอรมันจาก Wikipedia Römerberg http://commons.wikimedia.org/wiki/File:Weihnachtsbaum_R%C3%B6merberg.jpg


17

ฉันใช้หลามกับ opencv

อัลกอริทึมของฉันเป็นดังนี้:

  1. ก่อนอื่นจะนำช่องสีแดงออกจากภาพ
  2. ใช้เกณฑ์ (ค่าต่ำสุด 200) กับช่องสีแดง
  3. จากนั้นใช้ Morphological Gradient แล้วทำ 'Closing' (การขยายภาพตามด้วยการเซาะ)
  4. จากนั้นมันจะค้นหารูปทรงในระนาบและมันจะเลือกรูปทรงที่ยาวที่สุด

ผลลัพธ์ที่ได้:

รหัส:

import numpy as np
import cv2
import copy


def findTree(image,num):
    im = cv2.imread(image)
    im = cv2.resize(im, (400,250))
    gray = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY)
    imf = copy.deepcopy(im)

    b,g,r = cv2.split(im)
    minR = 200
    _,thresh = cv2.threshold(r,minR,255,0)
    kernel = np.ones((25,5))
    dst = cv2.morphologyEx(thresh, cv2.MORPH_GRADIENT, kernel)
    dst = cv2.morphologyEx(dst, cv2.MORPH_CLOSE, kernel)

    contours = cv2.findContours(dst,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)[0]
    cv2.drawContours(im, contours,-1, (0,255,0), 1)

    maxI = 0
    for i in range(len(contours)):
        if len(contours[maxI]) < len(contours[i]):
            maxI = i

    img = copy.deepcopy(r)
    cv2.polylines(img,[contours[maxI]],True,(255,255,255),3)
    imf[:,:,2] = img

    cv2.imshow(str(num), imf)

def main():
    findTree('tree.jpg',1)
    findTree('tree2.jpg',2)
    findTree('tree3.jpg',3)
    findTree('tree4.jpg',4)
    findTree('tree5.jpg',5)
    findTree('tree6.jpg',6)

    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()

ถ้าฉันเปลี่ยนเคอร์เนลจาก (25,5) เป็น (10,5) ฉันจะได้ผลลัพธ์ที่ดีกว่าบนต้นไม้ทั้งหมดยกเว้นด้านล่างซ้าย ป้อนคำอธิบายรูปภาพที่นี่

อัลกอริทึมของฉันอนุมานว่าต้นไม้มีแสงติดอยู่และในต้นไม้ด้านล่างซ้ายด้านบนมีแสงสว่างน้อยกว่าต้นไม้อื่น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.